pax_global_header 0000666 0000000 0000000 00000000064 14717463620 0014524 g ustar 00root root 0000000 0000000 52 comment=e4ec935d29c0aa2de38d9c12dd30d682f83b91c2
wayvnc-0.9.1/ 0000775 0000000 0000000 00000000000 14717463620 0013042 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/.builds/ 0000775 0000000 0000000 00000000000 14717463620 0014402 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/.builds/archlinux.yml 0000664 0000000 0000000 00000001506 14717463620 0017124 0 ustar 00root root 0000000 0000000 image: archlinux
packages:
- base-devel
- libglvnd
- libxkbcommon
- pixman
- gnutls
- jansson
- wayland
- wayland-protocols
- meson
# runtime deps for integration testing:
- sway
- jq
- lsof
- python-pycryptodomex # needed by vncdotool
- vncdotool
sources:
- http://github.com/any1/wayvnc
- http://github.com/any1/neatvnc
- http://github.com/any1/aml
tasks:
- aml: |
cd aml
meson --prefix=/usr build
ninja -C build
sudo ninja -C build install
- neatvnc: |
cd neatvnc
meson --prefix=/usr build
ninja -C build
sudo ninja -C build install
- build: |
cd wayvnc
meson --prefix=/usr build
ninja -C build
- test: |
cd wayvnc
ninja -C build test
- integration: |
cd wayvnc
./test/integration/integration.sh
wayvnc-0.9.1/.builds/freebsd.yml 0000664 0000000 0000000 00000001766 14717463620 0016551 0 ustar 00root root 0000000 0000000 image: freebsd/latest
packages:
- devel/meson
- devel/pkgconf
- devel/jansson
- devel/evdev-proto
- graphics/wayland
- graphics/libdrm
- graphics/libjpeg-turbo
- graphics/mesa-libs
- x11/pixman
- x11/libxkbcommon
- multimedia/ffmpeg
- security/gnutls
# runtime deps for integration testing:
- x11-wm/sway
- textproc/jq
- sysutils/lsof
- shells/bash
- devel/py-pip
sources:
- http://github.com/any1/wayvnc
- http://github.com/any1/neatvnc
- http://github.com/any1/aml
tasks:
- pip-vncdotool: |
sudo pip install vncdotool
- aml: |
cd aml
meson --prefix=/usr build
ninja -C build
sudo ninja -C build install
- neatvnc: |
cd neatvnc
meson --prefix=/usr build
ninja -C build
sudo ninja -C build install
- build: |
cd wayvnc
meson --prefix=/usr build
ninja -C build
- test: |
cd wayvnc
ninja -C build test
- integration: |
cd wayvnc
./test/integration/integration.sh
wayvnc-0.9.1/.github/ 0000775 0000000 0000000 00000000000 14717463620 0014402 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14717463620 0016565 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001540 14717463620 0021257 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`
- Try to reproduce while capturing a **trace log:**
- `wayvnc -Ltrace | tee wayvnc-crash.log`
- Get the **stack trace**:
- If have `coredumpctl`, you can gather the stack trace after a crash using
`coredumpctl gdb wayvnc` and then run `bt full` to obtain the stack trace.
- Otherwise, you can either locate the core file and load it into gdb or run
wayvnc in gdb like so:
- `gdb --args wayvnc -Ltrace`
- If the lines mentioning wayvnc, neatvnc or aml have `??`, please compile
wayvnc and those other projects from source with debug symbols and try
again.
- Describe how to **reproduce** the problem
wayvnc-0.9.1/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000312 14717463620 0020551 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"
wayvnc-0.9.1/.github/ISSUE_TEMPLATE/enhancement.md 0000664 0000000 0000000 00000000113 14717463620 0021367 0 ustar 00root root 0000000 0000000 ---
name: Enhancements
about: New functionality
labels: 'enhancement'
---
wayvnc-0.9.1/.github/pull_request_template.md 0000664 0000000 0000000 00000000072 14717463620 0021342 0 ustar 00root root 0000000 0000000 Please read CONTRIBUTING.md before making a pull request.
wayvnc-0.9.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14717463620 0016437 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/.github/workflows/build.yml 0000664 0000000 0000000 00000002146 14717463620 0020264 0 ustar 00root root 0000000 0000000 name: Build and Unit Test
on:
push:
branches: [ "master", "ci-test" ]
pull_request:
branches: [ "master" ]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: checkout aml
uses: actions/checkout@v4
with:
repository: any1/aml
path: subprojects/aml
- name: checkout neatvnc
uses: actions/checkout@v4
with:
repository: any1/neatvnc
path: subprojects/neatvnc
- name: prepare environment
run: |
sudo apt-get update
sudo apt-get install -y meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev libavutil-dev libturbojpeg0-dev scdoc
# runtime deps for integration testing:
sudo apt-get install -y sway jq lsof
pip install vncdotool
- name: configure
run: meson build -D tests=true
- name: compile
run: meson compile -C build
- name: unit tests
run: meson test --verbose -C build
- name: integration tests
run: ./test/integration/integration.sh
wayvnc-0.9.1/.gitignore 0000664 0000000 0000000 00000000136 14717463620 0015032 0 ustar 00root root 0000000 0000000 build
subprojects
*.swp
.clang_complete
.ycm_extra_conf.py
perf.*
*.pem
.vimrc
.cache
sandbox
wayvnc-0.9.1/CONTRIBUTING.md 0000664 0000000 0000000 00000011046 14717463620 0015275 0 ustar 00root root 0000000 0000000 # Contributing to wayvnc
## Commit Messages
Please, try to write good commit messages. Do your best to follow these 7 rules,
borrowed from [Chris Beams](https://chris.beams.io/posts/git-commit/), plus 1
extra rule:
1. Separate subject from body with a blank line
2. Limit the subject line to 50 characters
3. Capitalize the subject line
4. Do not end the subject line with a period
5. Use the imperative mood in the subject line
6. Wrap the body at 72 characters
7. Use the body to explain what and why vs. how
8. (Extra) Prefix the subject line with the component that's modified
If you wish to know why we follow these rules, please read Chris Beams' blog
entry, linked above.
Rule number 8 allows us to quickly gauge if a given commit is relevant to what
we're looking for when skimming the log. It adds consistency and simplifies the
message. For example
```
ctl-client: Print trailing newline for events
```
is better than
```
Print trailing newline for events in ctl-client
```
**Example:**
```
ctl-client: Print trailing newline for events
If someone wants to parse this instead of using jq, a trailing
newline delimits the end of the event.
```
## Style
This project follows the the
[Linux kernel's style guide](https://www.kernel.org/doc/html/latest/process/coding-style.html#codingstyle)
as far as coding style is concerned, with the following exceptions:
* When declaring pointer variables, the asterisk (`*`) is placed on the left
with the type rather than the variable name. Declaring multiple variables in
the same line is not allowed.
* Wrapped argument lists should not be aligned. Use two tabs instead. There is
a lot of code that uses aligned argument lists in the project, but I have
come to the conclusion that these alignments are not very nice to maintain.
### Summary & Examples:
In case you aren't familiar with Linux's coding style, here is a short summary
and some examples of acceptable formatting:
* Use tabs for indenting.
* We do not align code (mostly), but when we do, we use spaces rather than
tabs. This rule is not according to the Linux style guide.
* The preferred limit on the length of a single line is 80 columns.
* User-visible string such as log messages must not be split up.
* Use space after the following keywords: `if`, `switch`, `case`, `for`, `do`,
`while`.
* Do **not** use space after others such as: `sizeof`, `typeof`, `alignof`
or `__attribute__`.
* Do **not** use typedefs.
* It is allowed to use typedefs for function pointers. This rule is not
according to the Linux style guide.
#### Functions
```
static int do_something(int number, const char* text)
{
body of function
}
```
#### `if`
```
// Single statement
if (condition)
do_this();
// Multiple statements
if (condition) {
do_this(2, "41");
do_that();
}
// Single statement if/else
if (condition)
do_this();
else
do_that();
// Multi-statement if/else
if (condition) {
do_this();
do_that();
} else {
otherwise();
}
```
#### `switch`
```
switch (value) {
case 3:
printf("three!\n");
break;
case 5:
printf("five!\n");
break;
case 42:
printf("the answer to life, the universe and everything: ");
// fallthrough
default:
printf("%d\n", value);
break;
}
```
#### Arithmetic
```
int a = b * c + 5;
```
#### Pointers
```
char* some_text = "some text";
char* just_text = text + 5;
char t = *just_text;
char e = just_text[1];
```
## Testing
### Unit Tests
wayvnc has a small but growing set of unit tests, which are run on every GitHub
PR. To run them locally, do the following:
```bash
meson test -C build
```
### Integration Tests
There are also a handful of integration tests which also run on every PR. Read
the [integration tests documentation](test/integration/README.md) for more
details, but to run them locally:
```
./test/integration/integration.sh
```
### Valgrind
There is a helper script in [util/valgrind.sh](util/valgrind.sh) to aid in
memory profiling of wayvnc and wayvncctl. This can help find and eliminate
memory leaks.
### Automated Tests
We run a set of tests on every PR, in three different environments.
Each run ensures that the proposed code change:
1. Builds successfully
2. Passes all unit tests
3. Passes all integration tests
And does so in 3 different environments:
- Ubuntu as a [github action](.github/workflows/build.yml)
- Arch Linux as a [sourcehut build](.builds/archlinux.yml)
- FreeBSD as a [sourcehut build](.builds/freebsd.yaml)
## No Brown M&Ms
All pull requests must contain the following sentence in the description:
I have read and understood CONTRIBUTING.md.
wayvnc-0.9.1/COPYING 0000664 0000000 0000000 00000001343 14717463620 0014076 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.
wayvnc-0.9.1/FAQ.md 0000664 0000000 0000000 00000002512 14717463620 0013773 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.
wayvnc-0.9.1/FUNDING.yml 0000664 0000000 0000000 00000000044 14717463620 0014655 0 ustar 00root root 0000000 0000000 github: any1
patreon: andriyngvason
wayvnc-0.9.1/README.md 0000664 0000000 0000000 00000013305 14717463620 0014323 0 ustar 00root root 0000000 0000000 # wayvnc
[](https://github.com/any1/wayvnc/actions/workflows/build.yml)
[](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.
### wayvncctl control socket
To facilitate runtime interaction and control, wayvnc opens a unix domain socket
at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A
client can connect and exchange json-formatted IPC messages to query and control
the running wayvnc instance.
Use the `wayvncctl` utility to interact with this control socket from the
command line.
See the `wayvnc(1)` manpage for an in-depth description of the IPC protocol and
the available commands, and `wayvncctl(1)` for more on the command line
interface.
There is also a handy event-loop mode that can be used to run commands when
various events occur in wayvnc. See
[examples/event-watcher](examples/event-watcher) for more details.
wayvnc-0.9.1/examples/ 0000775 0000000 0000000 00000000000 14717463620 0014660 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/examples/README.md 0000664 0000000 0000000 00000002110 14717463620 0016131 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.
wayvnc-0.9.1/examples/auto-attach.py 0000775 0000000 0000000 00000004036 14717463620 0017452 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())
wayvnc-0.9.1/examples/event-watcher 0000775 0000000 0000000 00000003512 14717463620 0017363 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)
wayvnc-0.9.1/examples/single-output-sway 0000775 0000000 0000000 00000010356 14717463620 0020413 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)
wayvnc-0.9.1/include/ 0000775 0000000 0000000 00000000000 14717463620 0014465 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/include/buffer.h 0000664 0000000 0000000 00000006305 14717463620 0016113 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "sys/queue.h"
#include "config.h"
#include
#include
#include
#include
#include
#include
struct wl_buffer;
struct gbm_bo;
struct gbm_device;
struct nvnc_fb;
enum wv_buffer_type {
WV_BUFFER_UNSPEC = 0,
WV_BUFFER_SHM,
#ifdef ENABLE_SCREENCOPY_DMABUF
WV_BUFFER_DMABUF,
#endif
};
enum wv_buffer_domain {
WV_BUFFER_DOMAIN_UNSPEC = 0,
WV_BUFFER_DOMAIN_OUTPUT,
WV_BUFFER_DOMAIN_CURSOR,
};
#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;
TAILQ_ENTRY(wv_buffer) link;
LIST_ENTRY(wv_buffer) registry_link;
struct nvnc_fb* nvnc_fb;
struct wl_buffer* wl_buffer;
void* pixels;
size_t size;
int width, height, stride;
uint32_t format;
bool y_inverted;
enum wv_buffer_domain domain;
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;
};
TAILQ_HEAD(wv_buffer_queue, 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 wv_buffer_queue queue;
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);
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_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer);
void wv_buffer_registry_damage_all(struct pixman_region16* region,
enum wv_buffer_domain domain);
wayvnc-0.9.1/include/cfg.h 0000664 0000000 0000000 00000002760 14717463620 0015402 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(string, private_key_file) \
X(string, certificate_file) \
X(string, rsa_private_key_file) \
X(string, username) \
X(string, password) \
X(string, address) \
X(uint, port) \
X(bool, enable_pam) \
X(string, xkb_rules) \
X(string, xkb_model) \
X(string, xkb_layout) \
X(string, xkb_variant) \
X(string, xkb_options) \
X(bool, use_relative_paths) \
struct cfg {
char* directory;
#define string char*
#define uint uint32_t
#define X(type, name) type name;
X_CFG_LIST
#undef X
#undef uint
#undef string
};
int cfg_load(struct cfg* self, const char* path);
void cfg_destroy(struct cfg* self);
wayvnc-0.9.1/include/ctl-client.h 0000664 0000000 0000000 00000002601 14717463620 0016673 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);
wayvnc-0.9.1/include/ctl-commands.h 0000664 0000000 0000000 00000003577 14717463620 0017233 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_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[];
wayvnc-0.9.1/include/ctl-server.h 0000664 0000000 0000000 00000005517 14717463620 0016734 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "output.h"
#include
struct ctl;
struct cmd_response;
struct ctl_server_client;
struct ctl_server_client_info {
int id;
union {
struct sockaddr_storage address_storage;
struct sockaddr address;
};
const char* username;
const char* seat;
};
struct ctl_server_output {
char name[65];
char description[128];
unsigned height;
unsigned width;
bool captured;
char power[8];
};
struct ctl_server_actions {
void* userdata;
struct cmd_response* (*on_attach)(struct ctl*, const char* display);
struct cmd_response* (*on_detach)(struct ctl*);
struct cmd_response* (*on_output_cycle)(struct ctl*,
enum output_cycle_direction direction);
struct cmd_response* (*on_output_switch)(struct ctl*,
const char* output_name);
struct cmd_response* (*on_disconnect_client)(struct ctl*,
const char* id);
struct cmd_response* (*on_wayvnc_exit)(struct ctl*);
struct ctl_server_client *(*client_next)(struct ctl*,
struct ctl_server_client* prev);
void (*client_info)(const struct ctl_server_client*,
struct ctl_server_client_info* info);
// Return number of elements created
// Allocate 'outputs' array or set to NULL if none
// Receiver will free(outputs) when done.
int (*get_output_list)(struct ctl*,
struct ctl_server_output** outputs);
};
struct ctl* ctl_server_new(const char* socket_path,
const struct ctl_server_actions* actions);
void ctl_server_destroy(struct ctl*);
void* ctl_server_userdata(struct ctl*);
struct cmd_response* cmd_ok(void);
struct cmd_response* cmd_failed(const char* fmt, ...);
void ctl_server_event_connected(struct ctl*,
const struct ctl_server_client_info *info,
int new_connection_count);
void ctl_server_event_disconnected(struct ctl*,
const struct ctl_server_client_info *info,
int new_connection_count);
void ctl_server_event_capture_changed(struct ctl*,
const char* captured_output);
void ctl_server_event_detached(struct ctl*);
void ctl_server_event_output_added(struct ctl*, const char* name);
void ctl_server_event_output_removed(struct ctl*, const char* name);
wayvnc-0.9.1/include/data-control.h 0000664 0000000 0000000 00000003437 14717463620 0017234 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 "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* manager;
struct zwlr_data_control_device_v1* device;
struct zwlr_data_control_source_v1* selection;
struct zwlr_data_control_source_v1* primary_selection;
struct zwlr_data_control_offer_v1* offer;
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);
wayvnc-0.9.1/include/intset.h 0000664 0000000 0000000 00000002235 14717463620 0016146 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);
wayvnc-0.9.1/include/json-ipc.h 0000664 0000000 0000000 00000004475 14717463620 0016372 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);
wayvnc-0.9.1/include/keyboard.h 0000664 0000000 0000000 00000003131 14717463620 0016434 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 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*);
wayvnc-0.9.1/include/option-parser.h 0000664 0000000 0000000 00000003677 14717463620 0017455 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;
};
struct wv_option_value {
const struct wv_option* option;
char value[256];
};
struct option_parser {
const char* name;
const struct wv_option* options;
int n_opts;
struct wv_option_value values[128];
int n_values;
int position;
size_t remaining_argc;
const char* const* remaining_argv;
};
void option_parser_init(struct option_parser* self,
const struct wv_option* options);
void option_parser_print_usage(struct option_parser* self, FILE* stream);
int option_parser_print_arguments(struct option_parser* self, FILE* stream);
void option_parser_print_options(struct option_parser* self, FILE* stream);
int option_parser_parse(struct option_parser* self, int argc,
const char* const* argv);
const char* option_parser_get_value(const struct option_parser* self,
const char* name);
const char* option_parser_get_value_no_default(const struct option_parser* self,
const char* name);
void option_parser_print_cmd_summary(const char* summary, FILE* stream);
wayvnc-0.9.1/include/output-management.h 0000664 0000000 0000000 00000002111 14717463620 0020303 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;
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager);
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height);
void wlr_output_manager_destroy(void);
wayvnc-0.9.1/include/output.h 0000664 0000000 0000000 00000006066 14717463620 0016206 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
#include
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
struct zwlr_output_power_manager_v1;
struct zwlr_output_power_v1;
enum output_power_state {
OUTPUT_POWER_UNKNOWN = 0,
OUTPUT_POWER_OFF,
OUTPUT_POWER_ON,
};
const char* output_power_state_name(enum output_power_state state);
struct output {
struct wl_output* wl_output;
struct zxdg_output_v1* xdg_output;
struct zwlr_output_power_v1* wlr_output_power;
struct wl_list link;
uint32_t id;
uint32_t width;
uint32_t height;
uint32_t x;
uint32_t y;
enum wl_output_transform transform;
char make[256];
char model[256];
char name[256];
char description[256];
enum output_power_state power;
bool is_dimension_changed;
bool is_transform_changed;
bool is_headless;
void (*on_dimension_change)(struct output*);
void (*on_transform_change)(struct output*);
void (*on_power_change)(struct output*);
void* userdata;
};
struct output* output_new(struct wl_output* wl_output, uint32_t id);
void output_destroy(struct output* output);
void output_setup_xdg_output_managers(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);
enum output_cycle_direction {
OUTPUT_CYCLE_FORWARD,
OUTPUT_CYCLE_REVERSE,
};
struct output* output_cycle(const struct wl_list* list,
const struct output* current,
enum output_cycle_direction);
uint32_t output_get_transformed_width(const struct output* self);
uint32_t output_get_transformed_height(const struct output* self);
void output_transform_coord(const struct output* self,
uint32_t src_x, uint32_t src_y,
uint32_t* dst_x, uint32_t* dst_y);
void output_transform_box_coord(const struct output* self,
uint32_t src_x0, uint32_t src_y0,
uint32_t src_x1, uint32_t src_y1,
uint32_t* dst_x0, uint32_t* dst_y0,
uint32_t* dst_x1, uint32_t* dst_y1);
wayvnc-0.9.1/include/pam_auth.h 0000664 0000000 0000000 00000001547 14717463620 0016443 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);
wayvnc-0.9.1/include/pixels.h 0000664 0000000 0000000 00000002124 14717463620 0016141 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);
wayvnc-0.9.1/include/pointer.h 0000664 0000000 0000000 00000002400 14717463620 0016312 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"
#include "output.h"
struct pointer {
struct nvnc* vnc;
struct zwlr_virtual_pointer_v1* pointer;
enum nvnc_button_mask current_mask;
uint32_t current_x;
uint32_t current_y;
const struct output* output;
};
int pointer_init(struct pointer* self);
void pointer_destroy(struct pointer* self);
void pointer_set(struct pointer* self, uint32_t x, uint32_t y,
enum nvnc_button_mask button_mask);
wayvnc-0.9.1/include/screencopy-interface.h 0000664 0000000 0000000 00000004371 14717463620 0020753 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.
*/
#pragma once
#include "buffer.h"
#include
#include
struct wl_output;
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, void* userdata);
struct screencopy_impl {
enum screencopy_capabilitites caps;
struct screencopy* (*create)(struct wl_output*, bool render_cursor);
struct screencopy* (*create_cursor)(struct wl_output*, struct wl_seat*);
void (*destroy)(struct screencopy*);
int (*start)(struct screencopy*, bool immediate);
void (*stop)(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,
enum wv_buffer_domain domain, uint32_t format,
uint64_t modifier);
void* userdata;
};
struct screencopy* screencopy_create(struct wl_output* output,
bool render_cursor);
struct screencopy* screencopy_create_cursor(struct wl_output* output,
struct wl_seat* seat);
void screencopy_destroy(struct screencopy* self);
int screencopy_start(struct screencopy* self, bool immediate);
void screencopy_stop(struct screencopy* self);
wayvnc-0.9.1/include/seat.h 0000664 0000000 0000000 00000002533 14717463620 0015575 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);
wayvnc-0.9.1/include/shm.h 0000664 0000000 0000000 00000001522 14717463620 0015425 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);
wayvnc-0.9.1/include/smooth.h 0000664 0000000 0000000 00000002272 14717463620 0016152 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
/*
* This is an exponential smoothing filter with a time constant.
*
* The time constant must be set prior to applying the filter. It is, rougly
* speaking, the response time of the filter.
*
* See: https://en.wikipedia.org/wiki/Exponential_smoothing
*/
struct smooth {
double time_constant;
uint64_t last_time;
double last_result;
};
double smooth(struct smooth* self, double input);
wayvnc-0.9.1/include/strlcpy.h 0000664 0000000 0000000 00000001554 14717463620 0016343 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);
wayvnc-0.9.1/include/table-printer.h 0000664 0000000 0000000 00000003105 14717463620 0017405 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);
wayvnc-0.9.1/include/time-util.h 0000664 0000000 0000000 00000002660 14717463620 0016553 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);
}
wayvnc-0.9.1/include/transform-util.h 0000664 0000000 0000000 00000002451 14717463620 0017626 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
void wv_region_transform(struct pixman_region16 *dst,
struct pixman_region16 *src, enum wl_output_transform transform,
int width, int height);
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
enum wl_output_transform src, int width, int height);
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr);
enum wl_output_transform wv_output_transform_compose(
enum wl_output_transform tr_a, enum wl_output_transform tr_b);
wayvnc-0.9.1/include/tst.h 0000664 0000000 0000000 00000012725 14717463620 0015457 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);
wayvnc-0.9.1/include/usdt.h 0000664 0000000 0000000 00000002033 14717463620 0015613 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
wayvnc-0.9.1/include/util.h 0000664 0000000 0000000 00000002057 14717463620 0015617 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);
wayvnc-0.9.1/meson.build 0000664 0000000 0000000 00000011040 14717463620 0015200 0 ustar 00root root 0000000 0000000 project(
'wayvnc',
'c',
version: '0.9.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 = ['>=0.3.0', '<0.4.0']
neatvnc_version = ['>=0.9', '<0.10.0']
neatvnc_project = subproject(
'neatvnc',
required: false,
version: neatvnc_version,
)
aml_project = subproject('aml', required: false, version: aml_version)
if aml_project.found()
aml = aml_project.get_variable('aml_dep')
else
aml = dependency('aml', version: aml_version)
endif
if neatvnc_project.found()
neatvnc = neatvnc_project.get_variable('neatvnc_dep')
else
neatvnc = dependency('neatvnc', version: neatvnc_version)
endif
inc = include_directories('include')
subdir('protocols')
sources = [
'src/main.c',
'src/strlcpy.c',
'src/shm.c',
'src/screencopy.c',
'src/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/smooth.c',
'src/cfg.c',
'src/intset.c',
'src/buffer.c',
'src/pixels.c',
'src/transform-util.c',
'src/util.c',
'src/json-ipc.c',
'src/ctl-server.c',
'src/ctl-commands.c',
'src/option-parser.c',
'src/table-printer.c',
]
dependencies = [
libm,
librt,
pixman,
aml,
gbm,
drm,
wayland_client,
neatvnc,
xkbcommon,
client_protos,
jansson,
]
ctlsources = [
'src/wayvncctl.c',
'src/util.c',
'src/json-ipc.c',
'src/ctl-client.c',
'src/ctl-commands.c',
'src/strlcpy.c',
'src/option-parser.c',
'src/table-printer.c',
]
ctldependencies = [
jansson,
]
config = configuration_data()
config.set('PREFIX', '"' + prefix + '"')
if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h')
config.set('HAVE_USDT', true)
endif
if cc.has_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
wayvnc-0.9.1/meson_options.txt 0000664 0000000 0000000 00000000722 14717463620 0016500 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')
wayvnc-0.9.1/protocols/ 0000775 0000000 0000000 00000000000 14717463620 0015066 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/protocols/ext-foreign-toplevel-list-v1.xml 0000664 0000000 0000000 00000023446 14717463620 0023175 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.
wayvnc-0.9.1/protocols/ext-image-capture-source-v1.xml 0000664 0000000 0000000 00000011635 14717463620 0022761 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.
wayvnc-0.9.1/protocols/ext-image-copy-capture-v1.xml 0000664 0000000 0000000 00000045104 14717463620 0022431 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.
wayvnc-0.9.1/protocols/ext-transient-seat-v1.xml 0000664 0000000 0000000 00000011100 14717463620 0021664 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.
wayvnc-0.9.1/protocols/linux-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000042726 14717463620 0022355 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.
wayvnc-0.9.1/protocols/meson.build 0000664 0000000 0000000 00000002447 14717463620 0017237 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',
]
client_protos_src = []
client_protos_headers = []
foreach xml: client_protocols
client_protos_src += wayland_scanner_code.process(xml)
client_protos_headers += wayland_scanner_client.process(xml)
endforeach
lib_client_protos = static_library(
'client_protos',
client_protos_src + client_protos_headers,
dependencies: [
wayland_client
]
)
client_protos = declare_dependency(
link_with: lib_client_protos,
sources: client_protos_headers,
)
wayvnc-0.9.1/protocols/virtual-keyboard-unstable-v1.xml 0000664 0000000 0000000 00000011426 14717463620 0023237 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.
wayvnc-0.9.1/protocols/wlr-data-control-unstable-v1.xml 0000664 0000000 0000000 00000027416 14717463620 0023152 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.
wayvnc-0.9.1/protocols/wlr-export-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000021735 14717463620 0023336 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.
wayvnc-0.9.1/protocols/wlr-output-management-unstable-v1.xml 0000664 0000000 0000000 00000062266 14717463620 0024237 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.
wayvnc-0.9.1/protocols/wlr-output-power-management-unstable-v1.xml 0000664 0000000 0000000 00000012733 14717463620 0025363 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.
wayvnc-0.9.1/protocols/wlr-screencopy-unstable-v1.xml 0000664 0000000 0000000 00000023666 14717463620 0022740 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.
wayvnc-0.9.1/protocols/wlr-virtual-pointer-unstable-v1.xml 0000664 0000000 0000000 00000015357 14717463620 0023730 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.
wayvnc-0.9.1/protocols/xdg-output-unstable-v1.xml 0000664 0000000 0000000 00000022512 14717463620 0022071 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.
wayvnc-0.9.1/src/ 0000775 0000000 0000000 00000000000 14717463620 0013631 5 ustar 00root root 0000000 0000000 wayvnc-0.9.1/src/buffer.c 0000664 0000000 0000000 00000040567 14717463620 0015262 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 - 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
#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"
#ifdef ENABLE_SCREENCOPY_DMABUF
#include
#include
#include
#include
#ifdef HAVE_LINUX_DMA_HEAP
#include
#include
#define LINUX_CMA_PATH "/dev/dma_heap/linux,cma"
#endif // HAVE_LINUX_DMA_HEAP
#endif // ENABLE_SCREENCOPY_DMABUF
extern struct wl_shm* wl_shm;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
LIST_HEAD(wv_buffer_list, wv_buffer);
static struct wv_buffer_list buffer_registry;
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 (wl_shm)
type |= WV_BUFFER_SHM;
#ifdef ENABLE_SCREENCOPY_DMABUF
if (zwp_linux_dmabuf)
type |= WV_BUFFER_DMABUF;
#endif
return type;
}
struct wv_buffer* wv_buffer_create_shm(const struct wv_buffer_config* config)
{
assert(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(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->nvnc_fb = nvnc_fb_from_buffer(self->pixels, config->width,
config->height, config->format, config->stride / bpp);
if (!self->nvnc_fb) {
goto nvnc_fb_failure;
}
nvnc_set_userdata(self->nvnc_fb, self, NULL);
pixman_region_init(&self->frame_damage);
pixman_region_init_rect(&self->buffer_damage, 0, 0, config->width,
config->height);
LIST_INSERT_HEAD(&buffer_registry, self, registry_link);
close(fd);
return self;
nvnc_fb_failure:
wl_buffer_destroy(self->wl_buffer);
shm_failure:
pool_failure:
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
#ifdef ENABLE_SCREENCOPY_DMABUF
#ifdef HAVE_LINUX_DMA_HEAP
static bool have_linux_cma(void)
{
return access(LINUX_CMA_PATH, R_OK | W_OK) == 0;
}
static int linux_cma_alloc(size_t size)
{
int fd = open(LINUX_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(zwp_linux_dmabuf);
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
self->bo = have_linux_cma() ?
create_cma_gbm_bo(config->width, config->height,
config->format, gbm) :
gbm_bo_create_with_modifiers2(gbm->dev, config->width,
config->height, config->format,
config->modifiers, config->n_modifiers,
GBM_BO_USE_RENDERING);
#else
self->bo = gbm_bo_create_with_modifiers2(gbm->dev, config->width,
config->height, config->format, config->modifiers,
config->n_modifiers, GBM_BO_USE_RENDERING);
#endif
if (!self->bo)
goto bo_failure;
struct zwp_linux_buffer_params_v1* params;
params = zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf);
if (!params)
goto params_failure;
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->nvnc_fb = nvnc_fb_from_gbm_bo(self->bo);
if (!self->nvnc_fb) {
goto nvnc_fb_failure;
}
nvnc_set_userdata(self->nvnc_fb, self, NULL);
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);
LIST_INSERT_HEAD(&buffer_registry, self, registry_link);
return self;
nvnc_fb_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)
{
nvnc_fb_unref(self->nvnc_fb);
wl_buffer_destroy(self->wl_buffer);
munmap(self->pixels, self->size);
free(self);
}
#ifdef ENABLE_SCREENCOPY_DMABUF
static void wv_buffer_destroy_dmabuf(struct wv_buffer* self)
{
nvnc_fb_unref(self->nvnc_fb);
wl_buffer_destroy(self->wl_buffer);
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)
{
pixman_region_fini(&self->buffer_damage);
pixman_region_fini(&self->frame_damage);
LIST_REMOVE(self, registry_link);
switch (self->type) {
case WV_BUFFER_SHM:
wv_buffer_destroy_shm(self);
return;
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
wv_buffer_destroy_dmabuf(self);
return;
#endif
case WV_BUFFER_UNSPEC:;
}
abort();
}
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height)
{
pixman_region_union_rect(&self->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;
TAILQ_INIT(&self->queue);
if (config)
wv_buffer_pool_reconfig(self, config);
return self;
}
static void wv_buffer_pool_clear(struct wv_buffer_pool* pool)
{
while (!TAILQ_EMPTY(&pool->queue)) {
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
TAILQ_REMOVE(&pool->queue, buffer, link);
wv_buffer_destroy(buffer);
}
}
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool)
{
wv_buffer_pool_clear(pool);
free(pool->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
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");
wv_buffer_pool_clear(pool);
#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
}
static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
if (pool->config.type != buffer->type)
return false;
switch (pool->config.type) {
case WV_BUFFER_SHM:
return pool->config.stride == buffer->stride
&& pool->config.width == buffer->width
&& pool->config.height == buffer->height
&& pool->config.format == buffer->format;
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
return pool->config.width == buffer->width
&& pool->config.height == buffer->height
&& pool->config.format == buffer->format
&& pool->config.node == buffer->node
&& modifiers_match(pool->config.modifiers,
pool->config.n_modifiers,
buffer->modifiers, buffer->n_modifiers);
#endif
case WV_BUFFER_UNSPEC:
abort();
}
return false;
}
void wv_buffer_pool__on_release(struct nvnc_fb* fb, void* context)
{
struct wv_buffer* buffer = nvnc_get_userdata(fb);
struct wv_buffer_pool* pool = context;
wv_buffer_pool_release(pool, buffer);
}
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool)
{
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
if (buffer) {
assert(wv_buffer_pool_match_buffer(pool, buffer));
TAILQ_REMOVE(&pool->queue, buffer, link);
return buffer;
}
#ifdef ENABLE_SCREENCOPY_DMABUF
buffer = wv_buffer_create(&pool->config, pool->gbm);
#else
buffer = wv_buffer_create(&pool->config);
#endif
if (buffer)
nvnc_fb_set_release_fn(buffer->nvnc_fb,
wv_buffer_pool__on_release, pool);
return buffer;
}
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
wv_buffer_damage_clear(buffer);
if (wv_buffer_pool_match_buffer(pool, buffer)) {
TAILQ_INSERT_TAIL(&pool->queue, buffer, link);
} else {
wv_buffer_destroy(buffer);
}
}
void wv_buffer_registry_damage_all(struct pixman_region16* region,
enum wv_buffer_domain domain)
{
if (domain == WV_BUFFER_DOMAIN_UNSPEC)
return;
struct wv_buffer *buffer;
LIST_FOREACH(buffer, &buffer_registry, registry_link)
if (buffer->domain == domain)
pixman_region_union(&buffer->buffer_damage,
&buffer->buffer_damage, region);
}
wayvnc-0.9.1/src/cfg.c 0000664 0000000 0000000 00000006736 14717463620 0014550 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);
}
wayvnc-0.9.1/src/ctl-client.c 0000664 0000000 0000000 00000052014 14717463620 0016035 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_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;
}
wayvnc-0.9.1/src/ctl-commands.c 0000664 0000000 0000000 00000011351 14717463620 0016357 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 "ctl-commands.h"
#include
#include
struct cmd_info ctl_command_list[] = {
[CMD_ATTACH] = { "attach",
"Attach to a running wayland compositor",
{
{ "display", "Display name", "",
.positional = true },
{},
}
},
[CMD_DETACH] = { "detach",
"Detach from the wayland compositor",
{{}},
},
[CMD_HELP] = { "help",
"List all commands and events, or show usage of a specific command or event",
{
{ "command",
"The command to show (optional)",
"" },
{ "event",
"The event to show (optional)",
"" },
{},
}
},
[CMD_VERSION] = { "version",
"Query the version of the wayvnc process",
{{}}
},
[CMD_EVENT_RECEIVE] = { "event-receive",
"Register to begin receiving asynchronous events from wayvnc",
// TODO: Event type filtering?
{{}}
},
[CMD_CLIENT_LIST] = { "client-list",
"Return a list of all currently connected VNC sessions",
{{}}
},
[CMD_CLIENT_DISCONNECT] = { "client-disconnect",
"Disconnect a VNC session",
{
{ "id",
"The ID of the client to disconnect",
"", true },
{},
}
},
[CMD_OUTPUT_LIST] = { "output-list",
"Return a list of all currently detected Wayland outputs",
{{}}
},
[CMD_OUTPUT_CYCLE] = { "output-cycle",
"Cycle the actively captured output to the next available output, wrapping through all outputs.",
{{}}
},
[CMD_OUTPUT_SET] = { "output-set",
"Switch the actively captured output",
{
{ "output-name",
"The specific output name to capture",
"", true },
{},
}
},
[CMD_WAYVNC_EXIT] = { "wayvnc-exit",
"Disconnect all clients and shut down wayvnc",
{{}},
},
};
#define CLIENT_EVENT_PARAMS(including) \
{ "id", \
"A unique identifier for this client", \
"" }, \
{ "connection_count", \
"The total number of connected VNC clients " including " this one.", \
"" }, \
{ "address", \
"The IP address of this client (may be null)", \
"" }, \
{ "username", \
"The username used to authentice this client (may be null).", \
"" }, \
{},
struct cmd_info ctl_event_list[] = {
[EVT_CAPTURE_CHANGED] = {"capture-changed",
"Sent by wayvnc when the captured output is changed",
{
{ "output-name",
"The name of the output now being captured",
"" },
{},
},
},
[EVT_CLIENT_CONNECTED] = {"client-connected",
"Sent by wayvnc when a new VNC client connects",
{ CLIENT_EVENT_PARAMS("including") }
},
[EVT_CLIENT_DISCONNECTED] = {"client-disconnected",
"Sent by waynvc when a VNC client disconnects",
{ CLIENT_EVENT_PARAMS("not including") }
},
[EVT_DETACHED] = {"detached",
"Sent after detaching from compositor",
{}
},
[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));
}
wayvnc-0.9.1/src/ctl-server.c 0000664 0000000 0000000 00000064700 14717463620 0016072 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"
#define FAILED_TO(action) \
nvnc_log(NVNC_LOG_ERROR, "Failed to " action ": %m");
enum send_priority {
SEND_FIFO,
SEND_IMMEDIATE,
};
struct cmd {
enum cmd_type type;
};
struct cmd_attach {
struct cmd cmd;
char display[128];
};
struct cmd_help {
struct cmd cmd;
char id[64];
bool id_is_command;
};
struct cmd_set_output {
struct cmd cmd;
char target[64];
enum output_cycle_direction cycle;
};
struct cmd_disconnect_client {
struct cmd cmd;
char id[64];
};
struct cmd_response {
int code;
json_t* data;
};
struct ctl_client {
int fd;
struct wl_list link;
struct ctl* server;
struct aml_handler* handler;
char read_buffer[512];
size_t read_len;
json_t* response_queue;
char* write_buffer;
char* write_ptr;
size_t write_len;
bool drop_after_next_send;
bool accept_events;
};
struct ctl {
char socket_path[255];
struct ctl_server_actions actions;
int fd;
struct aml_handler* handler;
struct wl_list clients;
};
static struct cmd_response* cmd_response_new(int code, json_t* data)
{
struct cmd_response* new = calloc(1, sizeof(struct cmd_response));
new->code = code;
new->data = data;
return new;
}
static void cmd_response_destroy(struct cmd_response* self)
{
json_decref(self->data);
free(self);
}
static struct cmd_attach* cmd_attach_new(json_t* args,
struct jsonipc_error* err)
{
const char* display = NULL;
if (json_unpack(args, "{s:s}", "display", &display) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing display name");
return NULL;
}
struct cmd_attach* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->display, display, sizeof(cmd->display));
return cmd;
}
static struct cmd_help* cmd_help_new(json_t* args,
struct jsonipc_error* err)
{
const char* command = NULL;
const char* event = NULL;
if (args && json_unpack(args, "{s?s, s?s}",
"command", &command,
"event", &event) == -1) {
jsonipc_error_printf(err, EINVAL,
"expecting \"command\" or \"event\" (optional)");
return NULL;
}
if (command && event) {
jsonipc_error_printf(err, EINVAL,
"expecting exacly one of \"command\" or \"event\"");
return NULL;
}
struct cmd_help* cmd = calloc(1, sizeof(*cmd));
if (command) {
strlcpy(cmd->id, command, sizeof(cmd->id));
cmd->id_is_command = true;
} else if (event) {
strlcpy(cmd->id, event, sizeof(cmd->id));
cmd->id_is_command = false;
}
return cmd;
}
static struct cmd_set_output* cmd_set_output_new(json_t* args,
struct jsonipc_error* err)
{
const char* target = NULL;
if (json_unpack(args, "{s:s}", "output-name", &target) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing output name");
return NULL;
}
struct cmd_set_output* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->target, target, sizeof(cmd->target));
return cmd;
}
static struct cmd_disconnect_client* cmd_disconnect_client_new(json_t* args,
struct jsonipc_error* err)
{
const char* id = NULL;
if (json_unpack(args, "{s:s}", "id", &id) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing client id");
return NULL;
}
struct cmd_disconnect_client* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->id, id, sizeof(cmd->id));
return cmd;
}
static json_t* list_allowed(struct cmd_info (*list)[], size_t len)
{
json_t* allowed = json_array();
for (size_t i = 0; i < len; ++i) {
json_array_append_new(allowed, json_string((*list)[i].name));
}
return allowed;
}
static json_t* list_allowed_commands()
{
return list_allowed(&ctl_command_list, CMD_LIST_LEN);
}
static json_t* list_allowed_events()
{
return list_allowed(&ctl_event_list, EVT_LIST_LEN);
}
static struct cmd* parse_command(struct jsonipc_request* ipc,
struct jsonipc_error* err)
{
nvnc_trace("Parsing command %s", ipc->method);
enum cmd_type cmd_type = ctl_command_parse_name(ipc->method);
struct cmd* cmd = NULL;
switch (cmd_type) {
case CMD_ATTACH:
cmd = (struct cmd*)cmd_attach_new(ipc->params, err);
break;
case CMD_HELP:
cmd = (struct cmd*)cmd_help_new(ipc->params, err);
break;
case CMD_OUTPUT_SET:
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
break;
case CMD_CLIENT_DISCONNECT:
cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err);
break;
case CMD_DETACH:
case CMD_VERSION:
case CMD_EVENT_RECEIVE:
case CMD_CLIENT_LIST:
case CMD_OUTPUT_LIST:
case CMD_OUTPUT_CYCLE:
case CMD_WAYVNC_EXIT:
cmd = calloc(1, sizeof(*cmd));
break;
case CMD_UNKNOWN:
jsonipc_error_set_new(err, ENOENT,
json_pack("{s:o, s:o}",
"error",
jprintf("Unknown command \"%s\"",
ipc->method),
"commands", list_allowed_commands()));
break;
}
if (cmd)
cmd->type = cmd_type;
return cmd;
}
static void client_destroy(struct ctl_client* self)
{
nvnc_trace("Destroying client %p", self);
aml_stop(aml_get_default(), self->handler);
aml_unref(self->handler);
close(self->fd);
json_array_clear(self->response_queue);
json_decref(self->response_queue);
wl_list_remove(&self->link);
free(self);
}
static void set_internal_error(struct cmd_response** err, int code,
const char* fmt, ...)
{
char msg[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
nvnc_log(NVNC_LOG_WARNING, msg);
*err = cmd_response_new(code, json_pack("{s:s}", "error", msg));
}
// Return values:
// >0: Number of bytes read
// 0: No bytes read (EAGAIN)
// -1: Fatal error. Check 'err' for details, or if 'err' is null, terminate the connection.
static ssize_t client_read(struct ctl_client* self, struct cmd_response** err)
{
size_t bufferspace = sizeof(self->read_buffer) - self->read_len;
if (bufferspace == 0) {
set_internal_error(err, EIO, "Buffer overflow");
return -1;
}
ssize_t n = recv(self->fd, self->read_buffer + self->read_len, bufferspace,
MSG_DONTWAIT);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
nvnc_trace("recv: EAGAIN");
return 0;
}
set_internal_error(err, EIO, "Read failed: %m");
return -1;
} else if (n == 0) {
nvnc_log(NVNC_LOG_INFO, "Control socket client disconnected: %p", self);
errno = ENOTCONN;
return -1;
}
self->read_len += n;
nvnc_trace("Read %d bytes, total is now %d", n, self->read_len);
return n;
}
static json_t* client_next_object(struct ctl_client* self, struct cmd_response** ierr)
{
if (self->read_len == 0)
return NULL;
json_error_t err;
json_t* root = json_loadb(self->read_buffer, self->read_len,
JSON_DISABLE_EOF_CHECK, &err);
if (root) {
nvnc_log(NVNC_LOG_DEBUG, "<< %.*s", err.position, self->read_buffer);
advance_read_buffer(&self->read_buffer, &self->read_len, err.position);
} else if (json_error_code(&err) == json_error_premature_end_of_input) {
nvnc_trace("Awaiting more data");
} else {
set_internal_error(ierr, EINVAL, err.text);
}
return root;
}
static struct cmd_response* generate_help_object(const char* id, bool id_is_command)
{
struct cmd_info* info = id_is_command ?
ctl_command_by_name(id) :
ctl_event_by_name(id);
json_t* data;
if (!info) {
data = json_pack("{s:o, s:o}",
"commands", list_allowed_commands(),
"events", list_allowed_events());
} else {
json_t* param_list = NULL;
if (info->params[0].name) {
param_list = json_object();
for (struct cmd_param_info* param = info->params;
param->name; ++param)
json_object_set_new(param_list, param->name,
json_string(param->description));
}
data = json_pack("{s:{s:s, s:o*}}",
info->name,
"description", info->description,
"params", param_list);
}
struct cmd_response* response = cmd_ok();
response->data = data;
return response;
}
static struct cmd_response* generate_version_object()
{
struct cmd_response* response = cmd_ok();
response->data = json_pack("{s:s, s:s, s:s}",
"wayvnc", wayvnc_version,
"neatvnc", nvnc_version,
"aml", aml_version);
return response;
}
static struct ctl_server_client* ctl_server_client_first(struct ctl* self)
{
return self->actions.client_next(self, NULL);
}
static struct ctl_server_client* ctl_server_client_next(struct ctl* self,
struct ctl_server_client* prev)
{
return self->actions.client_next(self, prev);
}
static int sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr)
{
struct sockaddr_in* sa_in = (struct sockaddr_in*)addr;
struct sockaddr_in6* sa_in6 = (struct sockaddr_in6*)addr;
switch (addr->sa_family) {
case AF_INET:
inet_ntop(addr->sa_family, &sa_in->sin_addr, dst, sz);
return 0;
case AF_INET6:
inet_ntop(addr->sa_family, &sa_in6->sin6_addr, dst, sz);
return 0;
}
nvnc_log(NVNC_LOG_DEBUG,
"Don't know how to convert sa_family %d to string",
addr->sa_family);
return -1;
}
static void ctl_server_client_get_info(struct ctl* self,
const struct ctl_server_client* client,
struct ctl_server_client_info* info)
{
return self->actions.client_info(client, info);
}
static struct cmd_response* generate_vnc_client_list(struct ctl* self)
{
struct cmd_response* response = cmd_ok();
response->data = json_array();
struct ctl_server_client* client;
for (client = ctl_server_client_first(self); client;
client = ctl_server_client_next(self, client)) {
struct ctl_server_client_info info = {};
ctl_server_client_get_info(self, client, &info);
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info.id);
json_t* packed = json_pack("{s:s}", "id", id_str);
char address_string[256];
if (sockaddr_to_string(address_string, sizeof(address_string),
&info.address) == 0) {
json_object_set_new(packed, "address",
json_string(address_string));
}
if (info.username)
json_object_set_new(packed, "username",
json_string(info.username));
if (info.seat)
json_object_set_new(packed, "seat",
json_string(info.seat));
json_array_append_new(response->data, packed);
}
return response;
}
static struct cmd_response* generate_output_list(struct ctl* self)
{
struct ctl_server_output* outputs;
size_t num_outputs = self->actions.get_output_list(self, &outputs);
struct cmd_response* response = cmd_ok();
response->data = json_array();
for (size_t i = 0; i < num_outputs; ++i)
json_array_append_new(response->data, json_pack(
"{s:s, s:s, s:i, s:i, s:b, s:s}",
"name", outputs[i].name,
"description", outputs[i].description,
"height", outputs[i].height,
"width", outputs[i].width,
"captured", outputs[i].captured,
"power", outputs[i].power));
free(outputs);
return response;
}
static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
struct ctl_client* client, struct cmd* cmd)
{
const struct cmd_info* info = ctl_command_by_type(cmd->type);
assert(info);
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name);
struct cmd_response* response = NULL;
switch (cmd->type) {
case CMD_ATTACH:{
struct cmd_attach* c = (struct cmd_attach*)cmd;
response = self->actions.on_attach(self, c->display);
break;
}
case CMD_HELP:{
struct cmd_help* c = (struct cmd_help*)cmd;
response = generate_help_object(c->id, c->id_is_command);
break;
}
case CMD_OUTPUT_SET: {
struct cmd_set_output* c = (struct cmd_set_output*)cmd;
response = self->actions.on_output_switch(self, c->target);
break;
}
case CMD_CLIENT_DISCONNECT: {
struct cmd_disconnect_client* c =
(struct cmd_disconnect_client*)cmd;
response = self->actions.on_disconnect_client(self, c->id);
break;
}
case CMD_DETACH:
response = self->actions.on_detach(self);
break;
case CMD_WAYVNC_EXIT:
response = self->actions.on_wayvnc_exit(self);
break;
case CMD_VERSION:
response = generate_version_object();
break;
case CMD_EVENT_RECEIVE:
client->accept_events = true;
response = cmd_ok();
break;
case CMD_CLIENT_LIST:
response = generate_vnc_client_list(self);
break;
case CMD_OUTPUT_LIST:
response = generate_output_list(self);
break;
case CMD_OUTPUT_CYCLE:
response = self->actions.on_output_cycle(self, OUTPUT_CYCLE_FORWARD);
break;
case CMD_UNKNOWN:
break;
}
return response;
}
static void client_set_aml_event_mask(struct ctl_client* self)
{
int mask = AML_EVENT_READ;
if (json_array_size(self->response_queue) > 0 ||
self->write_len)
mask |= AML_EVENT_WRITE;
aml_set_event_mask(self->handler, mask);
}
static int client_enqueue(struct ctl_client* self, json_t* message,
enum send_priority priority)
{
int result;
switch(priority) {
case SEND_IMMEDIATE:
result = json_array_insert(self->response_queue, 0, message);
break;
case SEND_FIFO:
result = json_array_append(self->response_queue, message);
break;
}
client_set_aml_event_mask(self);
return result;
}
static int client_enqueue_jsonipc(struct ctl_client* self,
struct jsonipc_response* resp, enum send_priority priority)
{
int result = 0;
json_error_t err;
json_t* packed_response = jsonipc_response_pack(resp, &err);
if (!packed_response) {
nvnc_log(NVNC_LOG_WARNING, "Pack failed: %s", err.text);
result = -1;
goto failure;
}
result = client_enqueue(self, packed_response, priority);
json_decref(packed_response);
if (result != 0)
nvnc_log(NVNC_LOG_WARNING, "Append failed");
failure:
jsonipc_response_destroy(resp);
return result;
}
static int client_enqueue_error(struct ctl_client* self,
struct jsonipc_error* err, json_t* id)
{
struct jsonipc_response* resp = jsonipc_error_response_new(err, id);
return client_enqueue_jsonipc(self, resp, SEND_FIFO);
}
static int client_enqueue__response(struct ctl_client* self,
struct cmd_response* response, json_t* id,
enum send_priority priority)
{
nvnc_log(NVNC_LOG_INFO, "Enqueueing response: %s (%d)",
response->code == 0 ? "OK" : "FAILED", response->code);
char* str = NULL;
if (response->data)
str = json_dumps(response->data, 0);
nvnc_log(NVNC_LOG_DEBUG, "Response data: %s", str);
if(str)
free(str);
struct jsonipc_response* resp =
jsonipc_response_new(response->code, response->data, id);
cmd_response_destroy(response);
return client_enqueue_jsonipc(self, resp, priority);
}
static int client_enqueue_response(struct ctl_client* self,
struct cmd_response* response, json_t* id)
{
return client_enqueue__response(self, response, id, SEND_FIFO);
}
static int client_enqueue_internal_error(struct ctl_client* self,
struct cmd_response* err)
{
int result = client_enqueue__response(self, err, NULL, SEND_IMMEDIATE);
if (result != 0)
client_destroy(self);
self->drop_after_next_send = true;
return result;
}
static void send_ready(struct ctl_client* client)
{
if (client->write_buffer) {
nvnc_trace("Continuing partial write (%d left)", client->write_len);
} else if (json_array_size(client->response_queue) > 0){
nvnc_trace("Sending new queued message");
json_t* item = json_array_get(client->response_queue, 0);
client->write_len = json_dumpb(item, NULL, 0, JSON_COMPACT);
client->write_buffer = calloc(1, client->write_len);
client->write_ptr = client->write_buffer;
json_dumpb(item, client->write_buffer, client->write_len,
JSON_COMPACT);
nvnc_log(NVNC_LOG_DEBUG, ">> %.*s", client->write_len, client->write_buffer);
json_array_remove(client->response_queue, 0);
} else {
nvnc_trace("Nothing to send");
}
if (!client->write_ptr)
goto no_data;
ssize_t n = send(client->fd, client->write_ptr, client->write_len,
MSG_NOSIGNAL|MSG_DONTWAIT);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
nvnc_trace("send: EAGAIN");
goto send_eagain;
}
nvnc_log(NVNC_LOG_ERROR, "Could not send response: %m");
client_destroy(client);
return;
}
nvnc_trace("sent %d/%d bytes", n, client->write_len);
client->write_ptr += n;
client->write_len -= n;
send_eagain:
if (client->write_len == 0) {
nvnc_trace("Write buffer empty!");
free(client->write_buffer);
client->write_buffer = NULL;
client->write_ptr = NULL;
if (client->drop_after_next_send) {
nvnc_log(NVNC_LOG_WARNING, "Intentional disconnect");
client_destroy(client);
return;
}
} else {
nvnc_trace("Write buffer has %d remaining", client->write_len);
}
no_data:
client_set_aml_event_mask(client);
}
static void recv_ready(struct ctl_client* client)
{
struct ctl* server = client->server;
struct cmd_response* details = NULL;
switch (client_read(client, &details)) {
case 0: // Needs more data
return;
case -1: // Fatal error
if (details)
client_enqueue_internal_error(client, details);
else
client_destroy(client);
return;
default: // Read some data; check it
break;
}
json_t* root;
while (true) {
root = client_next_object(client, &details);
if (root == NULL)
break;
struct jsonipc_error jipc_err = JSONIPC_ERR_INIT;
struct jsonipc_request* request =
jsonipc_request_parse_new(root, &jipc_err);
if (!request) {
client_enqueue_error(client, &jipc_err,
NULL);
goto request_parse_failed;
}
struct cmd* cmd = parse_command(request, &jipc_err);
if (!cmd) {
client_enqueue_error(client, &jipc_err,
request->id);
goto cmdparse_failed;
}
// TODO: Enqueue the command (and request ID) to be
// handled by the main loop instead of doing the
// dispatch here
struct cmd_response* response =
ctl_server_dispatch_cmd(server, client, cmd);
if (!response)
goto no_response;
client_enqueue_response(client, response, request->id);
no_response:
free(cmd);
cmdparse_failed:
jsonipc_request_destroy(request);
request_parse_failed:
jsonipc_error_cleanup(&jipc_err);
json_decref(root);
}
if (details)
client_enqueue_internal_error(client, details);
}
static void on_ready(void* obj)
{
struct ctl_client* client = aml_get_userdata(obj);
uint32_t events = aml_get_revents(obj);
nvnc_trace("Client %p ready: 0x%x", client, events);
if (events & AML_EVENT_WRITE)
send_ready(client);
else if (events & AML_EVENT_READ)
recv_ready(client);
}
static void on_connection(void* obj)
{
nvnc_log(NVNC_LOG_DEBUG, "New connection");
struct ctl* server = aml_get_userdata(obj);
struct ctl_client* client = calloc(1, sizeof(*client));
if (!client) {
FAILED_TO("allocate a client object");
return;
}
client->server = server;
client->response_queue = json_array();
client->fd = accept(server->fd, NULL, 0);
if (client->fd < 0) {
FAILED_TO("accept a connection");
goto accept_failure;
}
client->handler = aml_handler_new(client->fd, on_ready, client, NULL);
if (!client->handler) {
FAILED_TO("create a loop handler");
goto handle_failure;
}
if (aml_start(aml_get_default(), client->handler) < 0) {
FAILED_TO("register for client events");
goto poll_start_failure;
}
wl_list_insert(&server->clients, &client->link);
nvnc_log(NVNC_LOG_INFO, "New control socket client connected: %p", client);
return;
poll_start_failure:
aml_unref(client->handler);
handle_failure:
close(client->fd);
accept_failure:
json_decref(client->response_queue);
free(client);
}
static int cleanup_old_socket(struct ctl* self, struct sockaddr* addr,
size_t addr_size)
{
struct stat sb;
if (stat(self->socket_path, &sb) == -1)
// Doesn't exist: safe to proceed.
return 0;
if (!S_ISSOCK(sb.st_mode)) {
nvnc_log(NVNC_LOG_ERROR, "Socket path '%s’ exists already and is not a socket.");
goto manual_intervention;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
FAILED_TO("open a temporary socket");
goto manual_intervention;
}
nvnc_log(NVNC_LOG_DEBUG, "Connecting to existing socket in case it's stale");
if (connect(fd, addr, addr_size) == 0) {
close(fd);
nvnc_log(NVNC_LOG_ERROR, "Another wayvnc process is already running.");
nvnc_log(NVNC_LOG_ERROR, "Use the '-S' option to choose an alternate control socket location");
return -1;
}
nvnc_log(NVNC_LOG_DEBUG, "Connect failed: %m");
close(fd);
nvnc_log(NVNC_LOG_WARNING, "Deleting stale control socket path \"%s\"", self->socket_path);
if (unlink(self->socket_path) == -1) {
FAILED_TO("remove stale unix socket");
goto manual_intervention;
}
return 0;
manual_intervention:
nvnc_log(NVNC_LOG_ERROR, "Manually remove \"%s\" or use the '-S' option to choose an alternate socket location", self->socket_path);
return -1;
}
int ctl_server_init(struct ctl* self, const char* socket_path)
{
if (!socket_path) {
socket_path = default_ctl_socket_path();
if (!getenv("XDG_RUNTIME_DIR"))
nvnc_log(NVNC_LOG_WARNING, "$XDG_RUNTIME_DIR is not set. Falling back to control socket \"%s\"", socket_path);
}
strlcpy(self->socket_path, socket_path, sizeof(self->socket_path));
nvnc_log(NVNC_LOG_DEBUG, "Initializing wayvncctl socket: %s", self->socket_path);
wl_list_init(&self->clients);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
if (strlen(self->socket_path) >= sizeof(addr.sun_path)) {
errno = ENAMETOOLONG;
FAILED_TO("create unix socket");
goto socket_failure;
}
strcpy(addr.sun_path, self->socket_path);
self->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (self->fd < 0) {
FAILED_TO("create unix socket");
goto socket_failure;
}
if (cleanup_old_socket(self, (struct sockaddr*)&addr, sizeof(addr)) != 0)
goto bind_failure;
if (bind(self->fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
FAILED_TO("bind unix socket");
goto bind_failure;
}
if (listen(self->fd, 16) < 0) {
FAILED_TO("listen to unix socket");
goto listen_failure;
}
self->handler = aml_handler_new(self->fd, on_connection, self, NULL);
if (!self->handler) {
FAILED_TO("create a main loop handler");
goto handle_failure;
}
if (aml_start(aml_get_default(), self->handler) < 0) {
FAILED_TO("Register for server events");
goto poll_start_failure;
}
return 0;
poll_start_failure:
aml_unref(self->handler);
handle_failure:
listen_failure:
unlink(self->socket_path);
bind_failure:
close(self->fd);
socket_failure:
return -1;
}
static void ctl_server_stop(struct ctl* self)
{
aml_stop(aml_get_default(), self->handler);
aml_unref(self->handler);
struct ctl_client* client;
struct ctl_client* tmp;
wl_list_for_each_safe(client, tmp, &self->clients, link)
client_destroy(client);
close(self->fd);
unlink(self->socket_path);
}
struct ctl* ctl_server_new(const char* socket_path,
const struct ctl_server_actions* actions)
{
struct ctl* ctl = calloc(1, sizeof(*ctl));
memcpy(&ctl->actions, actions, sizeof(*actions));
if (ctl_server_init(ctl, socket_path) != 0) {
free(ctl);
return NULL;
}
return ctl;
}
void ctl_server_destroy(struct ctl* self)
{
ctl_server_stop(self);
free(self);
}
void* ctl_server_userdata(struct ctl* self)
{
return self->actions.userdata;
}
struct cmd_response* cmd_ok()
{
return cmd_response_new(0, NULL);
}
struct cmd_response* cmd_failed(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
struct cmd_response* resp = cmd_response_new(1, json_pack("{s:o}",
"error", jvprintf(fmt, ap)));
va_end(ap);
return resp;
}
json_t* pack_connection_event_params(
const struct ctl_server_client_info *info,
int new_connection_count)
{
// TODO: Why is the id a string?
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info->id);
char address_string[256];
bool have_addr = sockaddr_to_string(address_string,
sizeof(address_string), &info->address) == 0;
return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}",
"id", id_str,
"address", have_addr ? address_string : NULL,
"username", info->username,
"seat", info->seat,
"connection_count", new_connection_count);
}
int ctl_server_enqueue_event(struct ctl* self, enum event_type evt_type,
json_t* params)
{
const char* event_name = ctl_event_list[evt_type].name;
char* param_str = json_dumps(params, JSON_COMPACT);
nvnc_log(NVNC_LOG_DEBUG, "Enqueueing %s event: %s", event_name, param_str);
free(param_str);
struct jsonipc_request* event = jsonipc_event_new(event_name, params);
json_decref(params);
json_error_t err;
json_t* packed_event = jsonipc_request_pack(event, &err);
jsonipc_request_destroy(event);
if (!packed_event) {
nvnc_log(NVNC_LOG_WARNING, "Could not pack %s event json: %s", event_name, err.text);
return -1;
}
int enqueued = 0;
struct ctl_client* client;
wl_list_for_each(client, &self->clients, link) {
if (!client->accept_events) {
nvnc_trace("Skipping event send to control client %p", client);
continue;
}
if (client_enqueue(client, packed_event, false) == 0) {
nvnc_trace("Enqueued event for control client %p", client);
enqueued++;
} else {
nvnc_trace("Failed to enqueue event for control client %p", client);
}
}
json_decref(packed_event);
nvnc_log(NVNC_LOG_DEBUG, "Enqueued %s event for %d clients", event_name, enqueued);
return enqueued;
}
static void ctl_server_event_connect(struct ctl* self,
enum event_type evt_type,
const struct ctl_server_client_info *info,
int new_connection_count)
{
json_t* params =
pack_connection_event_params(info, new_connection_count);
ctl_server_enqueue_event(self, evt_type, params);
}
void ctl_server_event_connected(struct ctl* self,
const struct ctl_server_client_info *info,
int new_connection_count)
{
ctl_server_event_connect(self, EVT_CLIENT_CONNECTED, info,
new_connection_count);
}
void ctl_server_event_disconnected(struct ctl* self,
const struct ctl_server_client_info *info,
int new_connection_count)
{
ctl_server_event_connect(self, EVT_CLIENT_DISCONNECTED, info,
new_connection_count);
}
void ctl_server_event_capture_changed(struct ctl* self,
const char* captured_output)
{
ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED,
json_pack("{s:s}", "output", captured_output));
}
void ctl_server_event_detached(struct ctl* self)
{
ctl_server_enqueue_event(self, EVT_DETACHED, json_object());
}
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));
}
wayvnc-0.9.1/src/data-control.c 0000664 0000000 0000000 00000027035 14717463620 0016373 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2020 Scott Moreau
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "data-control.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;
FILE* mem_fp;
size_t mem_size;
char* mem_data;
};
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);
if (ctx->mem_fp)
fclose(ctx->mem_fp);
free(ctx->mem_data);
close(ctx->fd);
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(void* handler)
{
struct receive_context* ctx = aml_get_userdata(handler);
int fd = aml_get_fd(handler);
assert(ctx->fd == fd);
char buf[4096];
ssize_t ret = read(fd, &buf, sizeof(buf));
if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
nvnc_log(NVNC_LOG_ERROR, "Clipboard read failed: %m");
destroy_receive_context(ctx);
} else if (ret > 0) {
fwrite(&buf, 1, ret, ctx->mem_fp);
return;
}
fclose(ctx->mem_fp);
ctx->mem_fp = NULL;
if (ctx->mem_size)
nvnc_send_cut_text(ctx->server, ctx->mem_data, ctx->mem_size);
destroy_receive_context(ctx);
}
static void on_send(void* 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 void receive_data(void* data,
struct zwlr_data_control_offer_v1* offer)
{
struct data_control* self = data;
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
nvnc_log(NVNC_LOG_ERROR, "pipe() failed: %m");
return;
}
if (dont_block(pipe_fd[0]) == -1) {
nvnc_log(NVNC_LOG_ERROR, "Failed to set O_NONBLOCK on clipbooard receive fd");
close(pipe_fd[0]);
close(pipe_fd[1]);
return;
}
struct receive_context* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
close(pipe_fd[0]);
close(pipe_fd[1]);
return;
}
zwlr_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]);
close(pipe_fd[1]);
ctx->fd = pipe_fd[0];
ctx->server = self->server;
ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size);
if (!ctx->mem_fp) {
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:
fclose(ctx->mem_fp);
open_memstream_failure:
free(ctx);
close(pipe_fd[0]);
}
static void data_control_offer(void* data,
struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1,
const char* mime_type)
{
struct data_control* self = data;
if (strcmp(mime_type, self->custom_mime_type_name) == 0) {
self->is_own_offer = true;
return;
}
if (self->offer)
return;
if (strcmp(mime_type, self->mime_type) == 0)
self->offer = zwlr_data_control_offer_v1;
}
struct zwlr_data_control_offer_v1_listener data_control_offer_listener = {
data_control_offer
};
static void data_control_device_offer(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
if (!id)
return;
zwlr_data_control_offer_v1_add_listener(id, &data_control_offer_listener, data);
}
static void data_control_device_selection(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
struct data_control* self = data;
if (!id) {
if (self->offer) {
zwlr_data_control_offer_v1_destroy(self->offer);
self->offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->offer && !self->is_own_offer)
receive_data(data, id);
zwlr_data_control_offer_v1_destroy(id);
self->offer = NULL;
self->is_own_offer = false;
}
static void data_control_device_finished(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1)
{
zwlr_data_control_device_v1_destroy(zwlr_data_control_device_v1);
}
static void data_control_device_primary_selection(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
struct data_control* self = data;
if (!id) {
if (self->offer) {
zwlr_data_control_offer_v1_destroy(self->offer);
self->offer = NULL;
self->is_own_offer = false;
}
return;
}
if (id == self->offer && !self->is_own_offer)
receive_data(data, id);
zwlr_data_control_offer_v1_destroy(id);
self->offer = NULL;
self->is_own_offer = false;
}
static struct zwlr_data_control_device_v1_listener data_control_device_listener = {
.data_offer = data_control_device_offer,
.selection = data_control_device_selection,
.finished = data_control_device_finished,
.primary_selection = data_control_device_primary_selection
};
static void
data_control_source_send(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1,
const char* mime_type,
int32_t fd)
{
struct data_control* self = data;
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_cancelled(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1)
{
struct data_control* self = data;
if (self->selection == zwlr_data_control_source_v1) {
self->selection = NULL;
}
if (self->primary_selection == zwlr_data_control_source_v1) {
self->primary_selection = NULL;
}
zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1);
}
struct zwlr_data_control_source_v1_listener data_control_source_listener = {
.send = data_control_source_send,
.cancelled = data_control_source_cancelled
};
static struct zwlr_data_control_source_v1* set_selection(struct data_control* self, bool primary) {
struct zwlr_data_control_source_v1* selection;
selection = zwlr_data_control_manager_v1_create_data_source(self->manager);
if (selection == NULL) {
nvnc_log(NVNC_LOG_ERROR, "zwlr_data_control_manager_v1_create_data_source() failed");
free(self->cb_data);
self->cb_data = NULL;
return NULL;
}
zwlr_data_control_source_v1_add_listener(selection, &data_control_source_listener, self);
zwlr_data_control_source_v1_offer(selection, self->mime_type);
zwlr_data_control_source_v1_offer(selection, self->custom_mime_type_name);
if (primary)
zwlr_data_control_device_v1_set_primary_selection(self->device, selection);
else
zwlr_data_control_device_v1_set_selection(self->device, selection);
return selection;
}
void data_control_init(struct data_control* self, struct nvnc* server, struct wl_seat* seat)
{
self->server = server;
LIST_INIT(&self->receive_contexts);
LIST_INIT(&self->send_contexts);
self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat);
zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self);
self->selection = NULL;
self->primary_selection = NULL;
self->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->selection) {
zwlr_data_control_source_v1_destroy(self->selection);
self->selection = NULL;
}
if (self->primary_selection) {
zwlr_data_control_source_v1_destroy(self->primary_selection);
self->primary_selection = NULL;
}
zwlr_data_control_device_v1_destroy(self->device);
free(self->cb_data);
}
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len)
{
if (!len) {
nvnc_log(NVNC_LOG_ERROR, "%s called with 0 length", __func__);
return;
}
free(self->cb_data);
self->cb_data = malloc(len);
if (!self->cb_data) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
return;
}
memcpy(self->cb_data, text, len);
self->cb_len = len;
// Set copy/paste buffer
self->selection = set_selection(self, false);
// Set highlight/middle_click buffer
self->primary_selection = set_selection(self, true);
}
wayvnc-0.9.1/src/ext-image-copy-capture.c 0000664 0000000 0000000 00000052043 14717463620 0020272 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
#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 "buffer.h"
#include "shm.h"
#include "time-util.h"
#include "usdt.h"
#include "pixels.h"
#include "config.h"
extern struct ext_output_image_capture_source_manager_v1* ext_output_image_capture_source_manager;
extern struct ext_image_copy_capture_manager_v1* ext_image_copy_capture_manager;
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 wl_output* wl_output;
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;
uint32_t frame_count;
uint32_t width, height;
uint32_t wl_shm_stride;
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;
};
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;
if (self->buffer)
wv_buffer_pool_release(self->pool, self->buffer);
self->buffer = NULL;
}
static int ext_image_copy_capture_init_session(struct ext_image_copy_capture* self)
{
struct ext_image_capture_source_v1* source;
source = ext_output_image_capture_source_manager_v1_create_source(
ext_output_image_capture_source_manager, self->wl_output);
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(
ext_image_copy_capture_manager, 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;
source = ext_output_image_capture_source_manager_v1_create_source(
ext_output_image_capture_source_manager, self->wl_output);
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(
ext_image_copy_capture_manager, 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)
{
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);
self->buffer->domain = self->cursor ? WV_BUFFER_DOMAIN_CURSOR :
WV_BUFFER_DOMAIN_OUTPUT;
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->cursor ? "cursor " : "", self->buffer,
100.0 * damage_area / pixel_area);
#endif
}
static void ext_image_copy_capture_schedule_from_timer(void* obj)
{
struct ext_image_copy_capture* self = aml_get_userdata(obj);
assert(self);
ext_image_copy_capture_schedule_capture(self);
}
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;
self->wl_shm_stride = width * 4;
}
static double rate_format(const struct ext_image_copy_capture* self,
enum wv_buffer_type type, enum wv_buffer_domain domain,
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, domain,
format, modifier);
}
static void rate_formats_in_array(const struct ext_image_copy_capture* self,
struct format_array* array, enum wv_buffer_type type)
{
enum wv_buffer_domain domain = WV_BUFFER_DOMAIN_OUTPUT;
if (self->cursor)
domain = WV_BUFFER_DOMAIN_CURSOR;
for (int i = 0; i < array->len; ++i) {
struct format_entry* entry = &array->entries[i];
entry->score = rate_format(self, type, domain, 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)
{
struct wv_buffer_config config = {
.width = self->width,
.height = self->height,
.type = WV_BUFFER_SHM,
.stride = self->wl_shm_stride,
};
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;
config.format = self->wl_shm_formats.entries[0].format;
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) {
ext_image_copy_capture_schedule_capture(self);
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)
{
bool is_cursor_session = self->cursor;
ext_image_copy_capture_deinit_session(self);
if (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_fb_set_transform(self->buffer->nvnc_fb, 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);
enum wv_buffer_domain domain = self->cursor ?
WV_BUFFER_DOMAIN_CURSOR : WV_BUFFER_DOMAIN_OUTPUT;
wv_buffer_registry_damage_all(&self->buffer->frame_damage, domain);
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++;
// TODO: Use presentation time somehow?
self->last_time = gettime_us();
self->parent.on_done(SCREENCOPY_DONE, buffer, 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_pool_release(self->pool, 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->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_fb_set_pts(self->buffer->nvnc_fb, 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 -1;
}
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 eps = 4000; // µs
uint64_t period = round(1e6 / self->parent.rate_limit);
uint64_t next_time = self->last_time + period - eps;
uint64_t now = gettime_us();
if (now >= next_time) {
aml_stop(aml_get_default(), self->timer);
ext_image_copy_capture_schedule_capture(self);
} else {
nvnc_trace("Scheduling %scapture after %"PRIu64" µs",
self->cursor ? "cursor " : "", next_time - now);
aml_set_duration(self->timer, next_time - now);
aml_start(aml_get_default(), self->timer);
}
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);
if (self->frame) {
ext_image_copy_capture_frame_v1_destroy(self->frame);
self->frame = NULL;
}
}
static struct screencopy* ext_image_copy_capture_create(struct wl_output* output,
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->wl_output = output;
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;
if (ext_image_copy_capture_init_session(self) < 0)
goto session_failure;
return (struct screencopy*)self;
session_failure:
wv_buffer_pool_destroy(self->pool);
failure:
free(self);
return NULL;
}
static struct screencopy* ext_image_copy_capture_create_cursor(struct wl_output* output,
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->wl_output = output;
self->wl_seat = seat;
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;
if (ext_image_copy_capture_init_cursor_session(self) < 0)
goto session_failure;
return (struct screencopy*)self;
session_failure:
wv_buffer_pool_destroy(self->pool);
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);
}
struct screencopy_impl ext_image_copy_capture_impl = {
.caps = SCREENCOPY_CAP_CURSOR | SCREENCOPY_CAP_TRANSFORM,
.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,
};
wayvnc-0.9.1/src/intset.c 0000664 0000000 0000000 00000004202 14717463620 0015301 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;
}
wayvnc-0.9.1/src/json-ipc.c 0000664 0000000 0000000 00000012600 14717463620 0015516 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 "json-ipc.h"
static const char* jsonipc_id_key = "id";
static const char* jsonipc_method_key = "method";
static const char* jsonipc_params_key = "params";
static const char* jsonipc_code_key = "code";
static const char* jsonipc_data_key = "data";
void jsonipc_error_set_new(struct jsonipc_error* err, int code, json_t* data)
{
if (!err)
return;
err->code = code;
err->data = data;
}
void jsonipc_error_printf(struct jsonipc_error* err, int code, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
jsonipc_error_set_new(err, code, json_pack("{s:o}", "error",
jvprintf(fmt, ap)));
va_end(ap);
}
void jsonipc_error_set_from_errno(struct jsonipc_error* err,
const char* context)
{
jsonipc_error_printf(err, errno, "%s: %m", context);
}
void jsonipc_error_cleanup(struct jsonipc_error* err)
{
if (!err)
return;
json_decref(err->data);
}
inline static bool is_valid_id(json_t* id)
{
return id == NULL ||
json_is_string(id) || json_is_number(id);
}
struct jsonipc_request* jsonipc_request_parse_new(json_t* root,
struct jsonipc_error* err)
{
struct jsonipc_request* ipc = calloc(1, sizeof(*ipc));
ipc->json = root;
json_incref(ipc->json);
json_error_t unpack_error;
if (json_unpack_ex(root, &unpack_error, 0, "{s:s, s?O, s?O}",
jsonipc_method_key, &ipc->method,
jsonipc_params_key, &ipc->params,
jsonipc_id_key, &ipc->id) == -1) {
jsonipc_error_printf(err, EINVAL, unpack_error.text);
goto failure;
}
if (!is_valid_id(ipc->id)) {
char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY);
jsonipc_error_printf(err, EINVAL,
"Invalid ID \"%s\"", id);
free(id);
goto failure;
}
return ipc;
failure:
jsonipc_request_destroy(ipc);
return NULL;
}
struct jsonipc_request* jsonipc_request__new(const char* method, json_t* params,
json_t* id)
{
struct jsonipc_request* ipc = calloc(1, sizeof(*ipc));
ipc->method = method;
ipc->params = params;
json_incref(ipc->params);
ipc->id = id;
return ipc;
}
static int request_id = 1;
struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params)
{
return jsonipc_request__new(method, params, json_integer(request_id++));
}
struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params)
{
return jsonipc_request__new(method, params, NULL);
}
struct jsonipc_request* jsonipc_event_parse_new(json_t* root,
struct jsonipc_error* err)
{
return jsonipc_request_parse_new(root, err);
}
json_t* jsonipc_request_pack(struct jsonipc_request* self, json_error_t* err)
{
return json_pack_ex(err, 0, "{s:s, s:O*, s:O*}",
jsonipc_method_key, self->method,
jsonipc_params_key, self->params,
jsonipc_id_key, self->id);
}
void jsonipc_request_destroy(struct jsonipc_request* self)
{
json_decref(self->params);
json_decref(self->id);
json_decref(self->json);
free(self);
}
struct jsonipc_response* jsonipc_response_parse_new(json_t* root,
struct jsonipc_error* err)
{
struct jsonipc_response* ipc = calloc(1, sizeof(*ipc));
ipc->json = root;
json_incref(ipc->json);
json_error_t unpack_error;
if (json_unpack_ex(root, &unpack_error, 0, "{s:i, s?O, s?O}",
jsonipc_code_key, &ipc->code,
jsonipc_data_key, &ipc->data,
jsonipc_id_key, &ipc->id) == -1) {
jsonipc_error_printf(err, EINVAL, unpack_error.text);
goto failure;
}
if (!is_valid_id(ipc->id)) {
char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY);
jsonipc_error_printf(err, EINVAL,
"Invalid ID \"%s\"", id);
free(id);
goto failure;
}
return ipc;
failure:
jsonipc_response_destroy(ipc);
return NULL;
}
struct jsonipc_response* jsonipc_response_new(int code,
json_t* data, json_t* id)
{
struct jsonipc_response* rsp = calloc(1, sizeof(*rsp));
rsp->code = code;
json_incref(id);
rsp->id = id;
json_incref(data);
rsp->data = data;
return rsp;
}
struct jsonipc_response* jsonipc_error_response_new(
struct jsonipc_error* err,
json_t* id)
{
return jsonipc_response_new(err->code, err->data, id);
}
void jsonipc_response_destroy(struct jsonipc_response* self)
{
json_decref(self->data);
json_decref(self->json);
json_decref(self->id);
free(self);
}
json_t* jsonipc_response_pack(struct jsonipc_response* self, json_error_t* err)
{
return json_pack_ex(err, 0, "{s:i, s:O*, s:O*}",
jsonipc_code_key, self->code,
jsonipc_id_key, self->id,
jsonipc_data_key, self->data);
}
json_t* jprintf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
json_t* result = jvprintf(fmt, args);
va_end(args);
return result;
}
json_t* jvprintf(const char* fmt, va_list ap)
{
char buffer[128];
int len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
return json_stringn(buffer, len);
}
wayvnc-0.9.1/src/keyboard.c 0000664 0000000 0000000 00000030006 14717463620 0015574 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.
*
* Acknowledgements: Reading Josef Gajdusek's wvnc code helped me understand
* how to use the xkbcommon API to interface with the wayland virtual keyboard
* interface.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "virtual-keyboard-unstable-v1.h"
#include "keyboard.h"
#include "shm.h"
#include "intset.h"
#define MAYBE_UNUSED __attribute__((unused))
struct table_entry {
xkb_keysym_t symbol;
xkb_keycode_t code;
int level;
};
struct kb_mods {
xkb_mod_mask_t depressed, latched, locked;
};
static void append_entry(struct keyboard* self, xkb_keysym_t symbol,
xkb_keycode_t code, int level)
{
if (self->lookup_table_size <= self->lookup_table_length) {
size_t new_size = self->lookup_table_size * 2;
struct table_entry* table =
realloc(self->lookup_table, new_size * sizeof(*table));
if (!table)
return; // TODO: Report this
self->lookup_table_size = new_size;
self->lookup_table = table;
}
struct table_entry* entry =
&self->lookup_table[self->lookup_table_length++];
entry->symbol = symbol;
entry->code = code;
entry->level = level;
}
static void key_iter(struct xkb_keymap* map, xkb_keycode_t code, void* userdata)
{
struct keyboard* self = userdata;
size_t n_levels = xkb_keymap_num_levels_for_key(map, code, 0);
for (size_t level = 0; level < n_levels; level++) {
const xkb_keysym_t* symbols;
size_t n_syms = xkb_keymap_key_get_syms_by_level(map, code, 0,
level,
&symbols);
for (size_t sym_idx = 0; sym_idx < n_syms; sym_idx++)
append_entry(self, symbols[sym_idx], code, level);
}
}
static int compare_symbols(const void* a, const void* b)
{
const struct table_entry* x = a;
const struct table_entry* y = b;
if (x->symbol == y->symbol)
return x->code < y->code ? -1 : x->code > y->code;
return x->symbol < y->symbol ? -1 : x->symbol > y->symbol;
}
static int compare_symbols2(const void* a, const void* b)
{
const struct table_entry* x = a;
const struct table_entry* y = b;
return x->symbol < y->symbol ? -1 : x->symbol > y->symbol;
}
static int create_lookup_table(struct keyboard* self)
{
self->lookup_table_length = 0;
self->lookup_table_size = 128;
self->lookup_table =
malloc(self->lookup_table_size * sizeof(*self->lookup_table));
if (!self->lookup_table)
return -1;
xkb_keymap_key_for_each(self->keymap, key_iter, self);
qsort(self->lookup_table, self->lookup_table_length,
sizeof(*self->lookup_table), compare_symbols);
return 0;
}
static char* get_symbol_name(xkb_keysym_t sym, char* dst, size_t size)
{
if (xkb_keysym_get_name(sym, dst, size) >= 0)
return dst;
snprintf(dst, size, "UNKNOWN (%x)", sym);
return dst;
}
static void keyboard__dump_entry(const struct keyboard* self,
const struct table_entry* entry)
{
char sym_name[256];
get_symbol_name(entry->symbol, sym_name, sizeof(sym_name));
const char* code_name MAYBE_UNUSED =
xkb_keymap_key_get_name(self->keymap, entry->code);
bool is_pressed MAYBE_UNUSED =
intset_is_set(&self->key_state, entry->code);
nvnc_log(NVNC_LOG_DEBUG, "symbol=%s level=%d code=%s %s", sym_name, entry->level,
code_name, is_pressed ? "pressed" : "released");
}
void keyboard_dump_lookup_table(const struct keyboard* self)
{
for (size_t i = 0; i < self->lookup_table_length; i++)
keyboard__dump_entry(self, &self->lookup_table[i]);
}
int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names)
{
self->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!self->context)
return -1;
if (intset_init(&self->key_state, 0) < 0)
goto key_state_failure;
self->keymap = xkb_keymap_new_from_names(self->context, rule_names, 0);
if (!self->keymap)
goto keymap_failure;
if (xkb_keymap_num_layouts(self->keymap) > 1)
nvnc_log(NVNC_LOG_WARNING, "Multiple keyboard layouts have been specified, but only one is supported.");
self->state = xkb_state_new(self->keymap);
if (!self->state)
goto state_failure;
if (create_lookup_table(self) < 0)
goto table_failure;
// keyboard_dump_lookup_table(self);
char* keymap_string =
xkb_keymap_get_as_string(self->keymap,
XKB_KEYMAP_FORMAT_TEXT_V1);
if (!keymap_string)
goto keymap_string_failure;
size_t keymap_size = strlen(keymap_string) + 1;
int keymap_fd = shm_alloc_fd(keymap_size);
if (keymap_fd < 0)
goto fd_failure;
size_t written = 0;
while (written < keymap_size) {
ssize_t ret = write(keymap_fd, keymap_string + written, keymap_size - written);
if (ret == -1 && errno == EINTR)
continue;
if (ret == -1)
goto write_failure;
written += ret;
}
free(keymap_string);
zwp_virtual_keyboard_v1_keymap(self->virtual_keyboard,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keymap_fd, keymap_size);
close(keymap_fd);
return 0;
write_failure:
close(keymap_fd);
fd_failure:
free(keymap_string);
keymap_string_failure:
free(self->lookup_table);
table_failure:
xkb_state_unref(self->state);
state_failure:
xkb_keymap_unref(self->keymap);
keymap_failure:
intset_destroy(&self->key_state);
key_state_failure:
xkb_context_unref(self->context);
return -1;
}
void keyboard_destroy(struct keyboard* self)
{
free(self->lookup_table);
xkb_state_unref(self->state);
xkb_keymap_unref(self->keymap);
intset_destroy(&self->key_state);
xkb_context_unref(self->context);
}
struct table_entry* keyboard_find_symbol(const struct keyboard* self,
xkb_keysym_t symbol)
{
struct table_entry cmp = { .symbol = symbol };
struct table_entry* entry =
bsearch(&cmp, self->lookup_table, self->lookup_table_length,
sizeof(*self->lookup_table), compare_symbols2);
if (!entry)
return NULL;
while (entry != self->lookup_table && (entry - 1)->symbol == symbol)
--entry;
return entry;
}
static void keyboard_send_mods(struct keyboard* self)
{
xkb_mod_mask_t depressed, latched, locked, group;
depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED);
latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED);
locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED);
group = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_EFFECTIVE);
zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed,
latched, locked, group);
}
static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code,
bool is_pressed)
{
enum xkb_state_component comp, compmask;
comp = xkb_state_update_key(self->state, code,
is_pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
compmask = XKB_STATE_MODS_DEPRESSED |
XKB_STATE_MODS_LATCHED |
XKB_STATE_MODS_LOCKED |
XKB_STATE_MODS_EFFECTIVE;
if (!(comp & compmask))
return;
keyboard_send_mods(self);
}
static struct table_entry* match_level(struct keyboard* self,
struct table_entry* entry)
{
xkb_keysym_t symbol = entry->symbol;
while (true) {
int level;
level = xkb_state_key_get_level(self->state, entry->code, 0);
if (entry->level == level)
return entry;
if (++entry >= &self->lookup_table[self->lookup_table_length] ||
entry->symbol != symbol)
break;
}
return NULL;
}
static bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
{
switch (symbol) {
case XKB_KEY_Shift_L:
case XKB_KEY_Shift_R:
case XKB_KEY_Control_L:
case XKB_KEY_Caps_Lock:
case XKB_KEY_Shift_Lock:
case XKB_KEY_Meta_L:
case XKB_KEY_Meta_R:
case XKB_KEY_Alt_L:
case XKB_KEY_Alt_R:
case XKB_KEY_Super_L:
case XKB_KEY_Super_R:
case XKB_KEY_Hyper_L:
case XKB_KEY_Hyper_R:
case XKB_KEY_ISO_Level5_Shift:
case XKB_KEY_ISO_Level5_Lock:
return true;
}
return false;
}
static void send_key(struct keyboard* self, xkb_keycode_t code, bool is_pressed)
{
zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, code - 8,
is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
: WL_KEYBOARD_KEY_STATE_RELEASED);
}
static void save_mods(struct keyboard* self, struct kb_mods* mods)
{
mods->depressed = xkb_state_serialize_mods(self->state,
XKB_STATE_MODS_DEPRESSED);
mods->latched = xkb_state_serialize_mods(self->state,
XKB_STATE_MODS_LATCHED);
mods->locked = xkb_state_serialize_mods(self->state,
XKB_STATE_MODS_LOCKED);
}
static void restore_mods(struct keyboard* self, struct kb_mods* mods)
{
xkb_state_update_mask(self->state, mods->depressed, mods->latched,
mods->locked, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED);
}
static void send_key_with_level(struct keyboard* self, xkb_keycode_t code,
bool is_pressed, int level)
{
struct kb_mods save;
save_mods(self, &save);
xkb_mod_mask_t mods = 0;
xkb_keymap_key_get_mods_for_level(self->keymap, code, 0, level,
&mods, 1);
xkb_state_update_mask(self->state, mods, 0, 0, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED);
keyboard_send_mods(self);
nvnc_log(NVNC_LOG_DEBUG, "send key with level: old mods: %x, new mods: %x",
save.latched | save.locked | save.depressed, mods);
send_key(self, code, is_pressed);
restore_mods(self, &save);
keyboard_send_mods(self);
}
static bool update_key_state(struct keyboard* self, xkb_keycode_t code,
bool is_pressed)
{
bool was_pressed = intset_is_set(&self->key_state, code);
if (was_pressed == is_pressed)
return false;
if (is_pressed)
intset_set(&self->key_state, code);
else
intset_clear(&self->key_state, code);
return true;
}
void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed)
{
struct table_entry* entry = keyboard_find_symbol(self, symbol);
if (!entry) {
char name[256];
nvnc_log(NVNC_LOG_ERROR, "Failed to look up keyboard symbol: %s",
get_symbol_name(symbol, name, sizeof(name)));
return;
}
bool level_is_match = true;
if (!keyboard_symbol_is_mod(symbol)) {
struct table_entry* level_entry = match_level(self, entry);
if (level_entry)
entry = level_entry;
else
level_is_match = false;
}
#ifndef NDEBUG
keyboard__dump_entry(self, entry);
#endif
if (!update_key_state(self, entry->code, is_pressed))
return;
keyboard_apply_mods(self, entry->code, is_pressed);
if (level_is_match)
send_key(self, entry->code, is_pressed);
else
send_key_with_level(self, entry->code, is_pressed,
entry->level);
}
void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code,
bool is_pressed)
{
if (update_key_state(self, code, is_pressed)) {
keyboard_apply_mods(self, code, is_pressed);
send_key(self, code, is_pressed);
}
}
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;
}
wayvnc-0.9.1/src/main.c 0000664 0000000 0000000 00000163727 14717463620 0014741 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wlr-screencopy-unstable-v1.h"
#include "ext-image-copy-capture-v1.h"
#include "ext-image-capture-source-v1.h"
#include "wlr-virtual-pointer-unstable-v1.h"
#include "virtual-keyboard-unstable-v1.h"
#include "xdg-output-unstable-v1.h"
#include "wlr-output-power-management-unstable-v1.h"
#include "wlr-output-management-unstable-v1.h"
#include "linux-dmabuf-unstable-v1.h"
#include "ext-transient-seat-v1.h"
#include "screencopy-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"
#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
#define MAYBE_UNUSED __attribute__((unused))
struct wayvnc_client;
enum socket_type {
SOCKET_TYPE_TCP = 0,
SOCKET_TYPE_UNIX,
SOCKET_TYPE_WEBSOCKET,
SOCKET_TYPE_FROM_FD,
};
struct wayvnc {
bool do_exit;
struct wl_display* display;
struct wl_registry* registry;
struct aml_handler* wl_handler;
struct wl_list outputs;
struct wl_list seats;
struct cfg cfg;
struct zwp_virtual_keyboard_manager_v1* keyboard_manager;
struct zwlr_virtual_pointer_manager_v1* pointer_manager;
struct zwlr_data_control_manager_v1* data_control_manager;
struct ext_transient_seat_manager_v1* transient_seat_manager;
struct output* selected_output;
struct seat* selected_seat;
struct screencopy* screencopy;
struct aml_handler* wayland_handler;
struct aml_signal* signal_handler;
struct nvnc* nvnc;
struct nvnc_display* nvnc_display;
const char* kb_layout;
const char* kb_variant;
uint32_t damage_area_sum;
uint32_t n_frames_captured;
bool disable_input;
bool use_transient_seat;
int nr_clients;
struct aml_ticker* performance_ticker;
struct aml_timer* capture_retry_timer;
struct ctl* ctl;
bool is_initializing;
bool start_detached;
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;
};
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,
void* userdata);
static void on_cursor_capture_done(enum screencopy_result result,
struct wv_buffer* buffer, 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,
const char* output);
static void wayland_detach(struct wayvnc* self);
static bool configure_cursor_sc(struct wayvnc* self,
struct wayvnc_client* client);
struct wl_shm* wl_shm = NULL;
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf = NULL;
struct zxdg_output_manager_v1* xdg_output_manager = NULL;
struct zwlr_output_power_manager_v1* wlr_output_power_manager = NULL;
struct zwlr_screencopy_manager_v1* screencopy_manager = NULL;
struct ext_output_image_capture_source_manager_v1*
ext_output_image_capture_source_manager = NULL;
struct ext_image_copy_capture_manager_v1* ext_image_copy_capture_manager = NULL;
extern struct screencopy_impl wlr_screencopy_impl, ext_image_copy_capture_impl;
static bool registry_add_input(void* data, struct wl_registry* registry,
uint32_t id, const char* interface,
uint32_t version)
{
struct wayvnc* self = data;
if (self->disable_input)
return false;
if (strcmp(interface, wl_seat_interface.name) == 0) {
struct wl_seat* wl_seat =
wl_registry_bind(registry, id, &wl_seat_interface, 7);
if (!wl_seat)
return true;
struct seat* seat = seat_new(wl_seat, id);
if (!seat) {
wl_seat_release(wl_seat);
return true;
}
wl_list_insert(&self->seats, &seat->link);
return true;
}
if (strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0) {
self->pointer_manager = wl_registry_bind(registry, id,
&zwlr_virtual_pointer_manager_v1_interface,
MIN(2, version));
return true;
}
if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) {
self->keyboard_manager = wl_registry_bind(registry, id,
&zwp_virtual_keyboard_manager_v1_interface,
1);
return true;
}
if (strcmp(interface, zwlr_data_control_manager_v1_interface.name) == 0) {
self->data_control_manager = wl_registry_bind(registry, id,
&zwlr_data_control_manager_v1_interface, 2);
return true;
}
if (strcmp(interface, ext_transient_seat_manager_v1_interface.name) == 0) {
self->transient_seat_manager = wl_registry_bind(registry, id,
&ext_transient_seat_manager_v1_interface, 1);
return true;
}
return false;
}
static void registry_add(void* data, struct wl_registry* registry,
uint32_t id, const char* interface,
uint32_t version)
{
struct wayvnc* self = data;
if (strcmp(interface, wl_output_interface.name) == 0) {
nvnc_trace("Registering new output %u", id);
struct wl_output* wl_output =
wl_registry_bind(registry, id, &wl_output_interface, 3);
if (!wl_output)
return;
struct output* output = output_new(wl_output, id);
if (!output)
return;
wl_list_insert(&self->outputs, &output->link);
if (!self->is_initializing) {
wl_display_dispatch(self->display);
wl_display_roundtrip(self->display);
ctl_server_event_output_added(self->ctl, output->name);
}
return;
}
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
nvnc_trace("Registering new xdg_output_manager");
xdg_output_manager =
wl_registry_bind(registry, id,
&zxdg_output_manager_v1_interface, 3);
output_setup_xdg_output_managers(&self->outputs);
return;
}
if (strcmp(interface, zwlr_output_power_manager_v1_interface.name) == 0) {
nvnc_trace("Registering new wlr_output_power_manager");
wlr_output_power_manager = wl_registry_bind(registry, id,
&zwlr_output_power_manager_v1_interface, 1);
return;
}
if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) {
nvnc_trace("Registering new wlr_output_manager");
struct zwlr_output_manager_v1* wlr_output_manager =
wl_registry_bind(registry, id,
&zwlr_output_manager_v1_interface, 1);
wlr_output_manager_setup(wlr_output_manager);
return;
}
if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
screencopy_manager =
wl_registry_bind(registry, id,
&zwlr_screencopy_manager_v1_interface,
MIN(3, version));
return;
}
#if 1
if (strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) == 0) {
ext_image_copy_capture_manager =
wl_registry_bind(registry, id,
&ext_image_copy_capture_manager_v1_interface,
MIN(1, version));
return;
}
if (strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) == 0) {
ext_output_image_capture_source_manager =
wl_registry_bind(registry, id,
&ext_output_image_capture_source_manager_v1_interface,
MIN(1, version));
return;
}
#endif
if (strcmp(interface, wl_shm_interface.name) == 0) {
wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
return;
}
if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
zwp_linux_dmabuf = wl_registry_bind(registry, id,
&zwp_linux_dmabuf_v1_interface, 3);
return;
}
if (registry_add_input(data, registry, id, interface, version))
return;
}
static void disconnect_seat_clients(struct wayvnc* self, struct seat* seat)
{
struct nvnc_client* nvnc_client;
for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client = nvnc_get_userdata(nvnc_client);
assert(client);
if (client->seat == seat) {
nvnc_client_close(nvnc_client);
}
}
}
static void registry_remove(void* data, struct wl_registry* registry,
uint32_t id)
{
struct wayvnc* self = data;
struct output* out = output_find_by_id(&self->outputs, id);
if (out) {
if (out == self->selected_output) {
nvnc_log(NVNC_LOG_WARNING, "Selected output %s went away",
out->name);
switch_to_prev_output(self);
} else
nvnc_log(NVNC_LOG_INFO, "Output %s went away", out->name);
ctl_server_event_output_removed(self->ctl, out->name);
wl_list_remove(&out->link);
output_destroy(out);
if (out == self->selected_output) {
if (self->start_detached) {
nvnc_log(NVNC_LOG_WARNING, "No fallback outputs left. Detaching...");
wayland_detach(self);
} else {
nvnc_log(NVNC_LOG_ERROR, "No fallback outputs left. Exiting...");
wayvnc_exit(self);
}
}
return;
}
struct seat* seat = seat_find_by_id(&self->seats, id);
if (seat) {
nvnc_log(NVNC_LOG_INFO, "Seat %s went away", seat->name);
disconnect_seat_clients(self, seat);
wl_list_remove(&seat->link);
seat_destroy(seat);
return;
}
}
static void wayland_detach(struct wayvnc* self)
{
if (!self->display)
return;
aml_stop(aml_get_default(), self->wl_handler);
aml_unref(self->wl_handler);
self->wl_handler = NULL;
// Screen blanking is required to release wl_shm of linux_dmabuf.
if (self->nvnc)
blank_screen(self);
if (self->nvnc) {
struct nvnc_client* nvnc_client;
for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client =
nvnc_get_userdata(nvnc_client);
client_detach_wayland(client);
}
}
self->selected_output = NULL;
output_list_destroy(&self->outputs);
seat_list_destroy(&self->seats);
if (zwp_linux_dmabuf)
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf);
zwp_linux_dmabuf = NULL;
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 (xdg_output_manager)
zxdg_output_manager_v1_destroy(xdg_output_manager);
xdg_output_manager = NULL;
if (wlr_output_power_manager)
zwlr_output_power_manager_v1_destroy(wlr_output_power_manager);
wlr_output_power_manager = NULL;
wlr_output_manager_destroy();
wl_shm_destroy(wl_shm);
wl_shm = NULL;
if (self->keyboard_manager)
zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager);
self->keyboard_manager = NULL;
if (self->pointer_manager)
zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager);
self->pointer_manager = NULL;
if (self->data_control_manager)
zwlr_data_control_manager_v1_destroy(self->data_control_manager);
self->data_control_manager = NULL;
if (self->performance_ticker) {
aml_stop(aml_get_default(), self->performance_ticker);
aml_unref(self->performance_ticker);
}
self->performance_ticker = NULL;
if (screencopy_manager)
zwlr_screencopy_manager_v1_destroy(screencopy_manager);
screencopy_manager = NULL;
if (ext_output_image_capture_source_manager)
ext_output_image_capture_source_manager_v1_destroy(
ext_output_image_capture_source_manager);
ext_output_image_capture_source_manager = NULL;
if (ext_image_copy_capture_manager)
ext_image_copy_capture_manager_v1_destroy(ext_image_copy_capture_manager);
ext_image_copy_capture_manager = 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->transient_seat_manager)
ext_transient_seat_manager_v1_destroy(self->transient_seat_manager);
wl_registry_destroy(self->registry);
self->registry = NULL;
wl_display_disconnect(self->display);
self->display = NULL;
if (self->ctl)
ctl_server_event_detached(self->ctl);
}
void wayvnc_destroy(struct wayvnc* self)
{
cfg_destroy(&self->cfg);
wayland_detach(self);
}
void on_wayland_event(void* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
int rc MAYBE_UNUSED = wl_display_prepare_read(self->display);
assert(rc == 0);
if (wl_display_read_events(self->display) < 0) {
if (errno == EPIPE || errno == ECONNRESET) {
nvnc_log(NVNC_LOG_ERROR, "Compositor has gone away. Exiting...");
if (self->start_detached)
wayland_detach(self);
else
wayvnc_exit(self);
return;
} else {
nvnc_log(NVNC_LOG_ERROR, "Failed to read wayland events: %m");
}
}
if (wl_display_dispatch_pending(self->display) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to dispatch pending");
wayland_detach(self);
// TODO: Re-attach
}
}
static int init_wayland(struct wayvnc* self, const char* display)
{
self->is_initializing = true;
static const struct wl_registry_listener registry_listener = {
.global = registry_add,
.global_remove = registry_remove,
};
self->display = wl_display_connect(display);
if (!self->display) {
const char* display_name = display ? display:
getenv("WAYLAND_DISPLAY");
if (!display_name) {
nvnc_log(NVNC_LOG_ERROR, "WAYLAND_DISPLAY is not set in the environment");
} else {
nvnc_log(NVNC_LOG_ERROR, "Failed to connect to WAYLAND_DISPLAY=\"%s\"", display_name);
nvnc_log(NVNC_LOG_ERROR, "Ensure wayland is running with that display name");
}
return -1;
}
wl_list_init(&self->outputs);
wl_list_init(&self->seats);
self->registry = wl_display_get_registry(self->display);
if (!self->registry) {
nvnc_log(NVNC_LOG_ERROR, "Could not locate the wayland compositor object registry");
goto failure;
}
wl_registry_add_listener(self->registry, ®istry_listener, self);
wl_display_dispatch(self->display);
wl_display_roundtrip(self->display);
self->is_initializing = false;
if (!self->pointer_manager && !self->disable_input) {
nvnc_log(NVNC_LOG_ERROR, "Virtual Pointer protocol not supported by compositor.");
nvnc_log(NVNC_LOG_ERROR, "wayvnc may still work if started with --disable-input.");
goto failure;
}
if (!self->keyboard_manager && !self->disable_input) {
nvnc_log(NVNC_LOG_ERROR, "Virtual Keyboard protocol not supported by compositor.");
nvnc_log(NVNC_LOG_ERROR, "wayvnc may still work if started with --disable-input.");
goto failure;
}
if (!screencopy_manager && !ext_image_copy_capture_manager) {
nvnc_log(NVNC_LOG_ERROR, "Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page.");
goto failure;
}
if (!self->transient_seat_manager && self->use_transient_seat) {
nvnc_log(NVNC_LOG_ERROR, "Transient seat protocol not supported by compositor");
goto failure;
}
self->wl_handler = aml_handler_new(wl_display_get_fd(self->display),
on_wayland_event, self, NULL);
if (!self->wl_handler)
goto failure;
int rc = aml_start(aml_get_default(), self->wl_handler);
if (rc < 0)
goto handler_failure;
return 0;
failure:
wl_display_disconnect(self->display);
self->display = NULL;
handler_failure:
if (self->wl_handler)
aml_unref(self->wl_handler);
self->wl_handler = NULL;
return -1;
}
void wayvnc_exit(struct wayvnc* self)
{
self->do_exit = true;
}
void on_signal(void* obj)
{
nvnc_log(NVNC_LOG_INFO, "Received termination signal.");
struct wayvnc* self = aml_get_userdata(obj);
wayvnc_exit(self);
}
struct cmd_response* on_output_cycle(struct ctl* ctl, enum output_cycle_direction direction)
{
struct wayvnc* self = ctl_server_userdata(ctl);
nvnc_log(NVNC_LOG_INFO, "ctl command: Rotating to %s output",
direction == OUTPUT_CYCLE_FORWARD ? "next" : "previous");
struct output* next = output_cycle(&self->outputs,
self->selected_output, direction);
switch_to_output(self, next);
return cmd_ok();
}
struct cmd_response* on_output_switch(struct ctl* ctl,
const char* output_name)
{
nvnc_log(NVNC_LOG_INFO, "ctl command: Switch to output \"%s\"", output_name);
struct wayvnc* self = ctl_server_userdata(ctl);
if (!output_name || output_name[0] == '\0')
return cmd_failed("Output name is required");
struct output* output = output_find_by_name(&self->outputs, output_name);
if (!output) {
return cmd_failed("No such output \"%s\"", output_name);
}
switch_to_output(self, output);
return cmd_ok();
}
static struct ctl_server_client *client_next(struct ctl* ctl,
struct ctl_server_client *prev)
{
struct wayvnc* self = ctl_server_userdata(ctl);
struct nvnc_client* vnc_prev = (struct nvnc_client*)prev;
return prev ? (struct ctl_server_client*)nvnc_client_next(vnc_prev) :
(struct ctl_server_client*)nvnc_client_first(self->nvnc);
}
static void compose_client_info(const struct wayvnc_client* client,
struct ctl_server_client_info* info)
{
info->id = client->id;
socklen_t addrlen = sizeof(info->address_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_get_userdata(vnc_client);
compose_client_info(client, info);
}
static int get_output_list(struct ctl* ctl,
struct ctl_server_output** outputs)
{
struct wayvnc* self = ctl_server_userdata(ctl);
int n = wl_list_length(&self->outputs);
if (n == 0) {
*outputs = NULL;
return 0;
}
*outputs = calloc(n, sizeof(**outputs));
struct output* output;
struct ctl_server_output* item = *outputs;
wl_list_for_each(output, &self->outputs, link) {
strlcpy(item->name, output->name, sizeof(item->name));
strlcpy(item->description, output->description,
sizeof(item->description));
item->height = output->height;
item->width = output->width;
item->captured = (output->id == self->selected_output->id);
strlcpy(item->power, output_power_state_name(output->power),
sizeof(item->power));
item++;
}
return n;
}
static struct cmd_response* on_disconnect_client(struct ctl* ctl,
const char* id_string)
{
char* endptr;
unsigned int id = strtoul(id_string, &endptr, 0);
if (!*id_string || *endptr)
return cmd_failed("Invalid client ID \"%s\"", id_string);
struct wayvnc* self = ctl_server_userdata(ctl);
for (struct nvnc_client* nvnc_client = nvnc_client_first(self->nvnc);
nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client = nvnc_get_userdata(nvnc_client);
if (client->id == id) {
nvnc_log(NVNC_LOG_WARNING, "Disconnecting client %d via control socket command",
client->id);
nvnc_client_close(nvnc_client);
return cmd_ok();
}
}
return cmd_failed("No such client with ID \"%s\"", id_string);
}
static struct cmd_response* on_wayvnc_exit(struct ctl* ctl)
{
struct wayvnc* self = ctl_server_userdata(ctl);
nvnc_log(NVNC_LOG_WARNING, "Shutting down via control socket command");
wayvnc_exit(self);
return cmd_ok();
}
int init_main_loop(struct wayvnc* self)
{
struct aml* loop = aml_get_default();
struct aml_signal* sig;
sig = aml_signal_new(SIGINT, on_signal, self, NULL);
if (!sig)
return -1;
int rc = aml_start(loop, sig);
aml_unref(sig);
if (rc < 0)
return -1;
return 0;
}
static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
enum nvnc_button_mask button_mask)
{
struct wayvnc_client* wv_client = nvnc_get_userdata(client);
struct wayvnc* wayvnc = wv_client->server;
if (!wv_client->pointer.pointer) {
return;
}
uint32_t xfx = 0, xfy = 0;
output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy);
pointer_set(&wv_client->pointer, xfx, xfy, button_mask);
}
static void on_key_event(struct nvnc_client* client, uint32_t symbol,
bool is_pressed)
{
struct wayvnc_client* wv_client = nvnc_get_userdata(client);
if (!wv_client->keyboard.virtual_keyboard) {
return;
}
keyboard_feed(&wv_client->keyboard, symbol, is_pressed);
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_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_get_userdata(nvnc_client);
if (client->data_control.manager) {
data_control_to_clipboard(&client->data_control, text, len);
}
}
static bool on_client_resize(struct nvnc_client* nvnc_client,
const struct nvnc_desktop_layout* layout)
{
struct wayvnc_client* client = nvnc_get_userdata(nvnc_client);
struct wayvnc* self = client->server;
uint16_t width = nvnc_desktop_layout_get_width(layout);
uint16_t height = nvnc_desktop_layout_get_height(layout);
struct output* output = client->server->selected_output;
if (output == NULL)
return false;
if (self->master_layout_client && self->master_layout_client != client)
return false;
self->master_layout_client = client;
nvnc_log(NVNC_LOG_DEBUG,
"Client resolution changed: %ux%u, capturing output %s which is headless: %s",
width, height, output->name,
output->is_headless ? "yes" : "no");
return wlr_output_manager_resize_output(output, width, height);
}
bool on_auth(const char* username, const char* password, void* ud)
{
struct wayvnc* self = ud;
#ifdef ENABLE_PAM
if (self->cfg.enable_pam)
return pam_auth(username, password);
#endif
if (strcmp(username, self->cfg.username) != 0)
return false;
if (strcmp(password, self->cfg.password) != 0)
return false;
return true;
}
static struct nvnc_fb* create_placeholder_buffer(uint16_t width, uint16_t height)
{
uint16_t stride = width;
struct nvnc_fb* fb = nvnc_fb_new(width, height, DRM_FORMAT_XRGB8888,
stride);
if (!fb)
return NULL;
size_t size = nvnc_fb_get_pixel_size(fb) * height * stride;
memset(nvnc_fb_get_addr(fb), 0x60, size);
return fb;
}
static int blank_screen(struct wayvnc* self)
{
int width = 1280;
int height = 720;
if (self->selected_output) {
width = output_get_transformed_width(self->selected_output);
height = output_get_transformed_height(self->selected_output);
}
struct nvnc_fb* placeholder_fb = create_placeholder_buffer(width, height);
if (!placeholder_fb) {
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate a placeholder buffer");
return -1;
}
struct pixman_region16 damage;
pixman_region_init_rect(&damage, 0, 0,
nvnc_fb_get_width(placeholder_fb),
nvnc_fb_get_height(placeholder_fb));
nvnc_display_feed_buffer(self->nvnc_display, placeholder_fb, &damage);
pixman_region_fini(&damage);
nvnc_fb_unref(placeholder_fb);
return 0;
}
static char* get_cfg_path(const struct cfg* cfg, char* dst, const char* src)
{
if (!cfg->use_relative_paths || src[0] == '/') {
strlcpy(dst, src, PATH_MAX);
return dst;
}
snprintf(dst, PATH_MAX, "%s/%s", cfg->directory, src);
return dst;
}
static int init_nvnc(struct wayvnc* self, const char* addr, uint16_t port,
enum socket_type socket_type)
{
switch (socket_type) {
case SOCKET_TYPE_TCP:
self->nvnc = nvnc_open(addr, port);
break;
case SOCKET_TYPE_UNIX:
self->nvnc = nvnc_open_unix(addr);
break;
case SOCKET_TYPE_WEBSOCKET:
self->nvnc = nvnc_open_websocket(addr, port);
break;
case SOCKET_TYPE_FROM_FD:;
int fd = atoi(addr);
self->nvnc = nvnc_open_from_fd(fd);
break;
default:
abort();
}
if (!self->nvnc) {
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);
self->nvnc_display = nvnc_display_new(0, 0);
if (!self->nvnc_display)
goto display_failure;
nvnc_add_display(self->nvnc, self->nvnc_display);
nvnc_set_userdata(self->nvnc, self, NULL);
nvnc_set_name(self->nvnc, "WayVNC");
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.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_pointer_fn(self->nvnc, on_pointer_event);
nvnc_set_key_fn(self->nvnc, on_key_event);
nvnc_set_key_code_fn(self->nvnc, on_key_code_event);
nvnc_set_new_client_fn(self->nvnc, on_nvnc_client_new);
nvnc_set_cut_text_fn(self->nvnc, on_client_cut_text);
if (blank_screen(self) != 0)
goto blank_screen_failure;
return 0;
blank_screen_failure:
auth_failure:
nvnc_display_unref(self->nvnc_display);
display_failure:
nvnc_close(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;
struct output* output = self->selected_output;
int rc = output_acquire_power_on(output);
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 && output->power != OUTPUT_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(void* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
aml_unref(self->capture_retry_timer);
self->capture_retry_timer = NULL;
wayvnc_start_capture_immediate(self);
}
static void wayvnc_restart_capture(struct wayvnc* self)
{
if (self->capture_retry_timer)
return;
int timeout = 100000;
self->capture_retry_timer = aml_timer_new(timeout,
on_capture_restart_timer, self, NULL);
aml_start(aml_get_default(), self->capture_retry_timer);
}
// TODO: Handle transform change too
void on_output_dimension_change(struct output* output)
{
struct wayvnc* self = output->userdata;
assert(self->selected_output == output);
if (self->nr_clients == 0)
return;
nvnc_log(NVNC_LOG_DEBUG, "Output dimensions changed. Restarting frame capturer...");
screencopy_stop(self->screencopy);
wayvnc_start_capture_immediate(self);
}
static void on_output_power_change(struct output* output)
{
nvnc_trace("Output %s power state changed to %s", output->name,
output_power_state_name(output->power));
struct wayvnc* self = output->userdata;
if (self->selected_output != output || self->nr_clients == 0)
return;
switch (output->power) {
case OUTPUT_POWER_ON:
wayvnc_start_capture_immediate(self);
wayvnc_start_cursor_capture(self, true);
break;
case OUTPUT_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 = self->selected_output->transform;
if (buffer->y_inverted) {
buffer_transform = wv_output_transform_compose(output_transform,
WL_OUTPUT_TRANSFORM_FLIPPED_180);
wv_region_transform(damage, &buffer->frame_damage,
WL_OUTPUT_TRANSFORM_FLIPPED_180,
buffer->width, buffer->height);
} else {
buffer_transform = output_transform;
pixman_region_copy(damage, &buffer->frame_damage);
}
nvnc_fb_set_transform(buffer->nvnc_fb,
(enum nvnc_transform)buffer_transform);
}
void wayvnc_process_frame(struct wayvnc* self, struct wv_buffer* buffer)
{
nvnc_trace("Passing on buffer: %p", buffer);
self->n_frames_captured++;
self->damage_area_sum +=
calculate_region_area(&buffer->frame_damage);
struct pixman_region16 damage;
pixman_region_init(&damage);
if (self->screencopy->impl->caps & SCREENCOPY_CAP_TRANSFORM) {
pixman_region_copy(&damage, &buffer->frame_damage);
} else {
apply_output_transform(self, buffer, &damage);
}
pixman_region_intersect_rect(&damage, &damage, 0, 0, buffer->width,
buffer->height);
nvnc_display_feed_buffer(self->nvnc_display, buffer->nvnc_fb,
&damage);
pixman_region_fini(&damage);
wayvnc_start_capture(self);
}
void on_capture_done(enum screencopy_result result, struct wv_buffer* buffer,
void* userdata)
{
struct wayvnc* self = userdata;
switch (result) {
case SCREENCOPY_FATAL:
nvnc_log(NVNC_LOG_ERROR, "Fatal error while capturing. Exiting...");
wayvnc_exit(self);
break;
case SCREENCOPY_FAILED:
wayvnc_restart_capture(self);
break;
case SCREENCOPY_DONE:
wayvnc_process_frame(self, buffer);
break;
}
}
int wayvnc_usage(struct option_parser* parser, FILE* stream, int rc)
{
fprintf(stream, "Usage: wayvnc");
option_parser_print_usage(parser, stream);
fprintf(stream, "\n");
option_parser_print_cmd_summary("Starts a VNC server for $WAYLAND_DISPLAY",
stream);
if (option_parser_print_arguments(parser, stream))
fprintf(stream, "\n");
option_parser_print_options(parser, stream);
fprintf(stream, "\n");
return rc;
}
int check_cfg_sanity(struct cfg* cfg)
{
if (cfg->enable_auth) {
int rc = 0;
if (!nvnc_has_auth()) {
nvnc_log(NVNC_LOG_ERROR, "Authentication can't be enabled because it was not selected during build");
rc = -1;
}
if (!!cfg->certificate_file != !!cfg->private_key_file) {
nvnc_log(NVNC_LOG_ERROR, "Need both certificate_file and private_key_file for TLS");
rc = -1;
}
if (!cfg->username && !cfg->enable_pam) {
nvnc_log(NVNC_LOG_ERROR, "Authentication enabled, but missing username");
rc = -1;
}
if (!cfg->password && !cfg->enable_pam) {
nvnc_log(NVNC_LOG_ERROR, "Authentication enabled, but missing password");
rc = -1;
}
if (cfg->relax_encryption) {
nvnc_log(NVNC_LOG_WARNING, "Authentication enabled with relaxed encryption; not all sessions are guaranteed to be encrypted");
}
return rc;
}
return 0;
}
static void on_perf_tick(void* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
double total_area = self->selected_output->width * self->selected_output->height;
double area_avg = (double)self->damage_area_sum / (double)self->n_frames_captured;
double relative_area_avg = 100.0 * area_avg / total_area;
nvnc_log(NVNC_LOG_INFO, "Frames captured: %"PRIu32", average reported frame damage: %.1f %%",
self->n_frames_captured, relative_area_avg);
self->n_frames_captured = 0;
self->damage_area_sum = 0;
}
static void start_performance_ticker(struct wayvnc* self)
{
if (!self->performance_ticker)
return;
aml_start(aml_get_default(), self->performance_ticker);
}
static void stop_performance_ticker(struct wayvnc* self)
{
if (!self->performance_ticker)
return;
aml_stop(aml_get_default(), self->performance_ticker);
}
static void client_init_wayland(struct wayvnc_client* self)
{
client_init_seat(self);
client_init_keyboard(self);
client_init_pointer(self);
client_init_data_control(self);
}
static void client_detach_wayland(struct wayvnc_client* self)
{
self->seat = NULL;
if (self->keyboard.virtual_keyboard) {
zwp_virtual_keyboard_v1_destroy(
self->keyboard.virtual_keyboard);
keyboard_destroy(&self->keyboard);
}
self->keyboard.virtual_keyboard = NULL;
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
self->pointer.pointer = NULL;
if (self->data_control.manager)
data_control_destroy(&self->data_control);
self->data_control.manager = NULL;
}
static unsigned next_client_id = 1;
static struct wayvnc_client* client_create(struct wayvnc* wayvnc,
struct nvnc_client* nvnc_client)
{
struct wayvnc_client* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->server = wayvnc;
self->nvnc_client = nvnc_client;
self->id = next_client_id++;
if (!wayvnc->cursor_master)
wayvnc->cursor_master = self;
if (wayvnc->display) {
client_init_wayland(self);
}
return self;
}
static void client_destroy(void* obj)
{
struct wayvnc_client* self = obj;
struct nvnc* nvnc = nvnc_client_get_server(self->nvnc_client);
struct wayvnc* wayvnc = nvnc_get_userdata(nvnc);
if (self == wayvnc->master_layout_client)
wayvnc->master_layout_client = NULL;
if (self == wayvnc->cursor_master) {
nvnc_set_cursor(wayvnc->nvnc, NULL, 0, 0, 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 (wayvnc->ctl) {
struct ctl_server_client_info info = {};
compose_client_info(self, &info);
ctl_server_event_disconnected(wayvnc->ctl, &info,
wayvnc->nr_clients);
}
if (wayvnc->nr_clients == 0 && wayvnc->display) {
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
screencopy_stop(wayvnc->screencopy);
output_release_power_on(wayvnc->selected_output);
stop_performance_ticker(wayvnc);
}
if (self->keyboard.virtual_keyboard) {
zwp_virtual_keyboard_v1_destroy(
self->keyboard.virtual_keyboard);
keyboard_destroy(&self->keyboard);
}
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
if (self->data_control.manager)
data_control_destroy(&self->data_control);
free(self);
}
static void handle_first_client(struct wayvnc* self)
{
nvnc_log(NVNC_LOG_INFO, "Starting screen capture");
start_performance_ticker(self);
wayvnc_start_capture_immediate(self);
}
static void on_nvnc_client_new(struct nvnc_client* client)
{
struct nvnc* nvnc = nvnc_client_get_server(client);
struct wayvnc* self = nvnc_get_userdata(nvnc);
struct wayvnc_client* wayvnc_client = client_create(self, client);
assert(wayvnc_client);
nvnc_set_userdata(client, wayvnc_client, client_destroy);
if (self->nr_clients++ == 0 && self->display) {
handle_first_client(self);
}
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
self->nr_clients);
struct ctl_server_client_info info = {};
compose_client_info(wayvnc_client, &info);
ctl_server_event_connected(self->ctl, &info, self->nr_clients);
}
void parse_keyboard_option(struct wayvnc* self, const char* arg)
{
// Find optional variant, separated by -
char* index = strchr(arg, '-');
if (index != NULL) {
self->kb_variant = index + 1;
// layout needs to be 0-terminated, replace the - by 0
*index = 0;
}
self->kb_layout = arg;
}
static void client_init_pointer(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
if (!wayvnc->pointer_manager)
return;
self->pointer.vnc = self->server->nvnc;
self->pointer.output = self->server->selected_output;
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
int pointer_manager_version =
zwlr_virtual_pointer_manager_v1_get_version(wayvnc->pointer_manager);
self->pointer.pointer = pointer_manager_version >= 2
? zwlr_virtual_pointer_manager_v1_create_virtual_pointer_with_output(
wayvnc->pointer_manager, self->seat->wl_seat,
wayvnc->selected_output->wl_output)
: zwlr_virtual_pointer_manager_v1_create_virtual_pointer(
wayvnc->pointer_manager, self->seat->wl_seat);
if (pointer_init(&self->pointer) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise pointer");
}
if (self == wayvnc->cursor_master) {
// Get seat capability update
// TODO: Make this asynchronous
wl_display_roundtrip(wayvnc->display);
wl_display_dispatch_pending(wayvnc->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 wayvnc* wayvnc = client->server;
struct seat* seat = seat_find_by_id(&wayvnc->seats, global_name);
assert(seat);
client->seat = seat;
}
static void handle_transient_seat_denied(void* data,
struct ext_transient_seat_v1* transient_seat)
{
(void)data;
(void)transient_seat;
// TODO: Something more graceful perhaps?
nvnc_log(NVNC_LOG_PANIC, "Transient seat denied");
}
static void client_init_transient_seat(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
self->transient_seat =
ext_transient_seat_manager_v1_create(wayvnc->transient_seat_manager);
static const struct ext_transient_seat_v1_listener listener = {
.ready = handle_transient_seat_ready,
.denied = handle_transient_seat_denied,
};
ext_transient_seat_v1_add_listener(self->transient_seat, &listener,
self);
// TODO: Make this asynchronous
wl_display_roundtrip(wayvnc->display);
assert(self->seat);
}
static void client_init_seat(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
if (wayvnc->disable_input)
return;
if (wayvnc->selected_seat) {
self->seat = wayvnc->selected_seat;
} else if (wayvnc->use_transient_seat) {
client_init_transient_seat(self);
} else {
self->seat = seat_find_unoccupied(&wayvnc->seats);
if (!self->seat) {
self->seat = seat_first(&wayvnc->seats);
}
}
if (self->seat)
self->seat->occupancy++;
}
static void client_init_keyboard(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
if (!wayvnc->keyboard_manager)
return;
self->keyboard.virtual_keyboard =
zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
wayvnc->keyboard_manager, self->seat->wl_seat);
struct xkb_rule_names rule_names = {
.rules = wayvnc->cfg.xkb_rules,
.layout = wayvnc->kb_layout ? wayvnc->kb_layout
: wayvnc->cfg.xkb_layout,
.model = wayvnc->cfg.xkb_model ? wayvnc->cfg.xkb_model
: "pc105",
.variant = wayvnc->kb_variant ? wayvnc->kb_variant :
wayvnc->cfg.xkb_variant,
.options = wayvnc->cfg.xkb_options,
};
if (keyboard_init(&self->keyboard, &rule_names) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise keyboard");
}
}
static void reinitialise_pointers(struct wayvnc* self)
{
struct nvnc_client* c;
for (c = nvnc_client_first(self->nvnc); c; c = nvnc_client_next(c)) {
struct wayvnc_client* client = nvnc_get_userdata(c);
client_init_pointer(client);
}
}
static void client_init_data_control(struct wayvnc_client* self)
{
struct wayvnc* wayvnc = self->server;
if (!wayvnc->data_control_manager)
return;
self->data_control.manager = wayvnc->data_control_manager;
data_control_init(&self->data_control, wayvnc->nvnc,
self->seat->wl_seat);
}
void log_selected_output(struct wayvnc* self)
{
nvnc_log(NVNC_LOG_INFO, "Capturing output %s",
self->selected_output->name);
struct output* output;
wl_list_for_each(output, &self->outputs, link) {
bool this_output = (output->id == self->selected_output->id);
nvnc_log(NVNC_LOG_INFO, "%s %s %dx%d+%dx%d Power:%s",
this_output ? ">>" : "--",
output->description,
output->width, output->height,
output->x, output->y,
output_power_state_name(output->power));
}
}
static void wayvnc_process_cursor(struct wayvnc* self, struct wv_buffer* buffer)
{
nvnc_log(NVNC_LOG_DEBUG, "Got new cursor");
bool is_damaged = pixman_region_not_empty(&buffer->frame_damage);
nvnc_set_cursor(self->nvnc, buffer->nvnc_fb, buffer->width,
buffer->height, buffer->x_hotspot, buffer->y_hotspot,
is_damaged);
wayvnc_start_cursor_capture(self, false);
}
static void on_cursor_capture_done(enum screencopy_result result,
struct wv_buffer* buffer, void* userdata)
{
struct wayvnc* self = userdata;
switch (result) {
case SCREENCOPY_FATAL:
nvnc_log(NVNC_LOG_ERROR, "Fatal error while capturing. Exiting...");
wayvnc_exit(self);
break;
case SCREENCOPY_FAILED:
wayvnc_start_cursor_capture(self, true);
break;
case SCREENCOPY_DONE:
wayvnc_process_cursor(self, buffer);
break;
}
}
static double rate_format(const void* userdata, enum wv_buffer_type type,
enum wv_buffer_domain domain, uint32_t format,
uint64_t modifier)
{
const struct wayvnc* self = userdata;
enum nvnc_fb_type fb_type = NVNC_FB_UNSPEC;
switch (type) {
case WV_BUFFER_SHM:
fb_type = NVNC_FB_SIMPLE;
break;
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
fb_type = NVNC_FB_GBM_BO;
break;
#endif
case WV_BUFFER_UNSPEC:;
}
assert(fb_type != NVNC_FB_UNSPEC);
switch (domain) {
case WV_BUFFER_DOMAIN_OUTPUT:
return nvnc_rate_pixel_format(self->nvnc, fb_type, format,
modifier);
case WV_BUFFER_DOMAIN_CURSOR:
return nvnc_rate_cursor_pixel_format(self->nvnc, fb_type, format,
modifier);
case WV_BUFFER_DOMAIN_UNSPEC:;
}
abort();
return -1;
}
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);
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->selected_output->wl_output, 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_format;
self->cursor_sc->userdata = self;
self->cursor_sc->rate_limit = self->max_rate;
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->selected_output->wl_output,
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_format;
self->screencopy->userdata = self;
self->screencopy->rate_limit = self->max_rate;
self->screencopy->enable_linux_dmabuf = self->enable_gpu_features;
return true;
}
void set_selected_output(struct wayvnc* self, struct output* output)
{
if (self->selected_output) {
self->selected_output->on_dimension_change = NULL;
}
self->selected_output = output;
output->on_dimension_change = on_output_dimension_change;
output->on_power_change = on_output_power_change;
output->userdata = self;
if (self->ctl)
ctl_server_event_capture_changed(self->ctl, output->name);
log_selected_output(self);
}
void switch_to_output(struct wayvnc* self, struct output* output)
{
if (self->selected_output == output) {
nvnc_log(NVNC_LOG_INFO, "Already selected output %s",
output->name);
return;
}
screencopy_stop(self->screencopy);
output_release_power_on(output);
set_selected_output(self, output);
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(&self->outputs,
self->selected_output, OUTPUT_CYCLE_FORWARD);
switch_to_output(self, next);
}
void switch_to_prev_output(struct wayvnc* self)
{
nvnc_log(NVNC_LOG_INFO, "Rotating to previous output");
struct output* prev = output_cycle(&self->outputs,
self->selected_output, OUTPUT_CYCLE_REVERSE);
switch_to_output(self, prev);
}
static char intercepted_error[256];
static void intercept_cmd_error(const struct nvnc_log_data* meta,
const char* message)
{
if (meta->level != NVNC_LOG_ERROR) {
nvnc_default_logger(meta, message);
return;
}
struct nvnc_log_data meta_override = *meta;
meta_override.level = NVNC_LOG_DEBUG;
nvnc_default_logger(&meta_override, message);
size_t len = strlen(intercepted_error);
if (len != 0 && len < sizeof(intercepted_error) - 2)
intercepted_error[len++] = '\n';
strlcpy(intercepted_error + len, message,
sizeof(intercepted_error) - len);
}
static struct cmd_response* on_attach(struct ctl* ctl, const char* display)
{
struct wayvnc* self = ctl_server_userdata(ctl);
assert(self);
memset(intercepted_error, 0, sizeof(intercepted_error));
nvnc_set_log_fn_thread_local(intercept_cmd_error);
// TODO: Add optional output argument
bool ok = wayland_attach(self, display, NULL);
nvnc_set_log_fn_thread_local(NULL);
return ok ? cmd_ok() : cmd_failed("%s", intercepted_error);
}
static bool wayland_attach(struct wayvnc* self, const char* display,
const char* output)
{
if (self->display) {
wayland_detach(self);
}
nvnc_log(NVNC_LOG_DEBUG, "Attaching to %s", display);
if (init_wayland(self, display) < 0) {
return false;
}
struct output* out;
if (output) {
out = output_find_by_name(&self->outputs, output);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output: %s", output);
wayland_detach(self);
return false;
}
} else {
out = output_first(&self->outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output available");
wayland_detach(self);
return false;
}
}
if (!screencopy_manager) {
nvnc_log(NVNC_LOG_ERROR, "Attached display does not implement wlr-screencopy-v1");
wayland_detach(self);
return false;
}
set_selected_output(self, out);
configure_screencopy(self);
struct nvnc_client* nvnc_client;
for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client = nvnc_get_userdata(nvnc_client);
client_init_wayland(client);
}
nvnc_log(NVNC_LOG_INFO, "Attached to %s", display);
if (self->nr_clients > 0) {
handle_first_client(self);
}
return true;
}
static struct cmd_response* on_detach(struct ctl* ctl)
{
struct wayvnc* self = ctl_server_userdata(ctl);
assert(self);
if (!self->display) {
return cmd_failed("Not attached!");
}
wayland_detach(self);
nvnc_log(NVNC_LOG_INFO, "Detached from wayland server");
return cmd_ok();
}
static int log_level_from_string(const char* str)
{
if (0 == strcmp(str, "quiet")) return NVNC_LOG_PANIC;
if (0 == strcmp(str, "error")) return NVNC_LOG_ERROR;
if (0 == strcmp(str, "warning")) return NVNC_LOG_WARNING;
if (0 == strcmp(str, "info")) return NVNC_LOG_INFO;
if (0 == strcmp(str, "debug")) return NVNC_LOG_DEBUG;
if (0 == strcmp(str, "trace")) return NVNC_LOG_TRACE;
return -1;
}
int show_version(void)
{
printf("wayvnc: %s\n", wayvnc_version);
printf("neatvnc: %s\n", nvnc_version);
printf("aml: %s\n", aml_version);
return 0;
}
int main(int argc, char* argv[])
{
struct wayvnc self = { 0 };
const char* cfg_file = NULL;
bool enable_gpu_features = false;
const char* address = NULL;
int port = 0;
bool use_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* keyboard_options = NULL;
bool overlay_cursor = false;
bool show_performance = false;
int max_rate = 30;
bool disable_input = false;
bool use_transient_seat = false;
int log_level = NVNC_LOG_WARNING;
static const struct wv_option opts[] = {
{ .positional = "address",
.help = "The IP address or unix socket path to listen on.",
.default_ = DEFAULT_ADDRESS},
{ .positional = "port",
.help = "The TCP port to listen on.",
.default_ = XSTR(DEFAULT_PORT)},
{ 'C', "config", "",
"Select a config file." },
{ 'd', "disable-input", NULL,
"Disable all remote input." },
{ 'D', "detached", NULL,
"Start detached from a compositor." },
{ 'f', "max-fps", "",
"Set rate limit.",
.default_ = "30" },
{ '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" },
{ '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." },
{ '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);
}
cfg_file = option_parser_get_value(&option_parser, "config");
enable_gpu_features = !!option_parser_get_value(&option_parser, "gpu");
output_name = option_parser_get_value(&option_parser, "output");
seat_name = option_parser_get_value(&option_parser, "seat");
socket_path = option_parser_get_value(&option_parser, "socket");
overlay_cursor = !!option_parser_get_value(&option_parser, "render-cursor");
show_performance = !!option_parser_get_value(&option_parser,
"show-performance");
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");
log_level = option_parser_get_value(&option_parser, "verbose")
? NVNC_LOG_INFO : NVNC_LOG_WARNING;
log_level = log_level_from_string(
option_parser_get_value(&option_parser, "log-level"));
max_rate = atoi(option_parser_get_value(&option_parser, "max-fps"));
use_transient_seat = !!option_parser_get_value(&option_parser,
"transient-seat");
start_detached = !!option_parser_get_value(&option_parser, "detached");
self.enable_resizing = !option_parser_get_value(&option_parser,
"disable-resizing");
self.start_detached = start_detached;
self.overlay_cursor = overlay_cursor;
self.max_rate = max_rate;
self.enable_gpu_features = enable_gpu_features;
keyboard_options = option_parser_get_value(&option_parser, "keyboard");
if (keyboard_options)
parse_keyboard_option(&self, keyboard_options);
nvnc_set_log_level(log_level);
// Only check for explicitly-set values here (defaults applied below)
address = option_parser_get_value_no_default(&option_parser, "address");
const char* port_str = option_parser_get_value_no_default(&option_parser,
"port");
if (port_str)
port = atoi(port_str);
if (seat_name && disable_input) {
nvnc_log(NVNC_LOG_ERROR, "seat and disable-input are conflicting options");
return 1;
}
int n_address_modifiers = use_unix_socket + use_websocket +
use_external_fd;
if (n_address_modifiers > 1) {
nvnc_log(NVNC_LOG_ERROR, "Only one of the websocket, 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;
}
errno = 0;
int cfg_rc = cfg_load(&self.cfg, cfg_file);
if (cfg_rc != 0 && (cfg_file || errno != ENOENT)) {
if (cfg_rc > 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to load config. Error on line %d",
cfg_rc);
} else {
nvnc_log(NVNC_LOG_ERROR, "Failed to load config. %m");
}
return 1;
}
if (check_cfg_sanity(&self.cfg) < 0)
return 1;
if (cfg_rc == 0) {
if (!address) address = self.cfg.address;
if (!port) port = self.cfg.port;
}
if (!address) address = DEFAULT_ADDRESS;
if (!port) port = DEFAULT_PORT;
self.disable_input = disable_input;
self.use_transient_seat = use_transient_seat;
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;
if (!start_detached) {
if (init_wayland(&self, NULL) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise wayland");
goto wayland_failure;
}
struct output* out;
if (output_name) {
out = output_find_by_name(&self.outputs, output_name);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output");
goto wayland_failure;
}
} else {
out = output_first(&self.outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output found");
goto wayland_failure;
}
}
set_selected_output(&self, out);
struct seat* seat = NULL;
if (seat_name) {
seat = seat_find_by_name(&self.seats, seat_name);
if (!seat) {
nvnc_log(NVNC_LOG_ERROR, "No such seat");
goto wayland_failure;
}
}
self.selected_seat = seat;
}
if (aml_unstable_abi_version != AML_UNSTABLE_API)
nvnc_log(NVNC_LOG_PANIC, "libaml is incompatible with this build of wayvnc!");
enum socket_type socket_type = SOCKET_TYPE_TCP;
if (use_unix_socket)
socket_type = SOCKET_TYPE_UNIX;
else if (use_websocket)
socket_type = SOCKET_TYPE_WEBSOCKET;
else if (use_external_fd)
socket_type = SOCKET_TYPE_FROM_FD;
if (!start_detached && !configure_screencopy(&self))
goto screencopy_failure;
if (show_performance)
self.performance_ticker = aml_ticker_new(1000000, on_perf_tick,
&self, NULL);
const struct ctl_server_actions ctl_actions = {
.userdata = &self,
.on_attach = on_attach,
.on_detach = on_detach,
.on_output_cycle = on_output_cycle,
.on_output_switch = on_output_switch,
.client_next = client_next,
.client_info = client_info,
.get_output_list = get_output_list,
.on_disconnect_client = on_disconnect_client,
.on_wayvnc_exit = on_wayvnc_exit,
};
self.ctl = ctl_server_new(socket_path, &ctl_actions);
if (!self.ctl)
goto ctl_server_failure;
if (init_nvnc(&self, address, port, socket_type) < 0)
goto nvnc_failure;
if (self.display)
wl_display_dispatch_pending(self.display);
while (!self.do_exit) {
if (self.display)
wl_display_flush(self.display);
aml_poll(aml, -1);
aml_dispatch(aml);
}
nvnc_log(NVNC_LOG_INFO, "Exiting...");
if (self.display)
screencopy_stop(self.screencopy);
ctl_server_destroy(self.ctl);
self.ctl = NULL;
nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc);
self.nvnc = NULL;
wayvnc_destroy(&self);
if (zwp_linux_dmabuf)
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf);
if (self.screencopy)
screencopy_destroy(self.screencopy);
aml_unref(aml);
return 0;
nvnc_failure:
ctl_server_destroy(self.ctl);
ctl_server_failure:
screencopy_failure:
wayland_failure:
aml_unref(aml);
failure:
self.nvnc = NULL;
wayvnc_destroy(&self);
return 1;
}
wayvnc-0.9.1/src/option-parser.c 0000664 0000000 0000000 00000023076 14717463620 0016607 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.
*/
#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;
for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional)
continue;
const char* open = "<";
const char* close = ">";
if (opt->default_) {
open = "[";
close = ""; // Closed via optional_paren_count loop below
optional_paren_count++;
} else {
// Enforce there must be NO non-optional args after
// we've processed at least one optional arg
assert(optional_paren_count == 0);
}
fprintf(stream, " %s%s%s", open, opt->positional, close);
}
for (int i = 0; i < optional_paren_count; ++i)
fprintf(stream, "]");
}
int option_parser_print_arguments(struct option_parser* self, FILE* stream)
{
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;
self->position += 1;
return opt->is_subcommand ? 0 : 1;
}
int option_parser_parse(struct option_parser* self, int argc,
const char* const* argv)
{
int i = 1;
while (i < argc) {
if (argv[i][0] == '-') {
if (argv[i][1] == '-') {
if (argv[i][2] == '\0') {
i++;
break;
}
int rc = parse_long_arg(self, argc, argv, i);
if (rc < 0)
return -1;
i += rc;
} else {
int rc = parse_short_args(self, argc, argv, i);
if (rc < 0)
return -1;
i += rc;
}
} else {
int rc = parse_positional_arg(self, argc, argv, i);
if (rc < 0)
return -1;
if (rc == 0)
break;
i += rc;
}
}
self->remaining_argc = argc - i;
if (self->remaining_argc)
self->remaining_argv = argv + i;
return 0;
}
const char* option_parser_get_value_no_default(const struct option_parser* self,
const char* name)
{
const struct wv_option* opt;
bool is_short = name[0] && !name[1];
for (int i = 0; i < self->n_values; ++i) {
const struct wv_option_value* value = &self->values[i];
opt = value->option;
if (is_short) {
if (opt->short_opt && opt->short_opt == *name)
return value->value;
} else {
if (opt->long_opt && strcmp(opt->long_opt, name) == 0)
return value->value;
}
if (opt->positional && strcmp(opt->positional, name) == 0)
return value->value;
}
return NULL;
}
const char* option_parser_get_value(const struct option_parser* self,
const char* name)
{
const char* value = option_parser_get_value_no_default(self, name);
if (value)
return value;
bool is_short = name[0] && !name[1];
const struct wv_option* opt;
if (is_short) {
opt = find_short_option(self, name[0]);
if (opt)
return opt->default_;
} else {
opt = find_long_option(self, name);
if (opt)
return opt->default_;
opt = find_positional_option_by_name(self, name);
if (opt)
return opt->default_;
}
return NULL;
}
void option_parser_print_cmd_summary(const char* summary, FILE* stream)
{
struct table_printer printer;
table_printer_init(&printer, stream);
fprintf(stream, "\n");
table_printer_indent_and_reflow_text(stream, summary, printer.max_width, 0, 0);
fprintf(stream, "\n");
}
wayvnc-0.9.1/src/output-management.c 0000664 0000000 0000000 00000017343 14717463620 0017457 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_head {
struct zwlr_output_head_v1* head;
struct wl_list link;
char* name;
bool enabled;
};
static struct wl_list heads;
static uint32_t last_config_serial;
static struct zwlr_output_manager_v1* wlr_output_manager;
/* single head properties */
static void output_head_name(void* data,
struct zwlr_output_head_v1* output_head, const char* name)
{
struct output_manager_head* head = data;
nvnc_trace("Got head name: %s", name);
free(head->name);
head->name = strdup(name);
}
static void output_head_description(void* data,
struct zwlr_output_head_v1* output_head,
const char* description)
{
nvnc_trace("Got head description: %s", description);
}
static void output_head_physical_size(void* data,
struct zwlr_output_head_v1* output_head,
int32_t width, int32_t height)
{
nvnc_trace("Got head size: %dx%d", width, height);
}
static void output_head_mode(void* data,
struct zwlr_output_head_v1* output_head,
struct zwlr_output_mode_v1* mode)
{
nvnc_trace("Got head mode");
}
static void output_head_enabled(void* data,
struct zwlr_output_head_v1* output_head, int32_t enabled)
{
nvnc_trace("Got head enabled: %s", enabled ? "yes" : "no");
struct output_manager_head* head = data;
head->enabled = !!enabled;
}
static void output_head_current_mode(void* data,
struct zwlr_output_head_v1* output_head,
struct zwlr_output_mode_v1* mode)
{
nvnc_trace("Got head current mode");
}
static void output_head_position(void* data,
struct zwlr_output_head_v1* output_head, int32_t x, int32_t y)
{
nvnc_trace("Got head position: %d,%d", x, y);
}
static void output_head_transform(void* data,
struct zwlr_output_head_v1* output_head, int32_t transform)
{
nvnc_trace("Got head transform: %d", transform);
}
static void output_head_scale(void* data,
struct zwlr_output_head_v1* output_head, wl_fixed_t scale_f)
{
#ifndef NDEBUG
double scale = wl_fixed_to_double(scale_f);
nvnc_trace("Got head scale: %.2f", scale);
#endif
}
static void output_head_finished(void* data,
struct zwlr_output_head_v1* output_head)
{
nvnc_trace("head gone, removing");
struct output_manager_head* head = data;
zwlr_output_head_v1_destroy(output_head);
wl_list_remove(&head->link);
free(head->name);
head->name = NULL;
head->head = NULL;
free(head);
}
struct zwlr_output_head_v1_listener wlr_output_head_listener = {
.name = output_head_name,
.description = output_head_description,
.physical_size = output_head_physical_size,
.mode = output_head_mode,
.enabled = output_head_enabled,
.current_mode = output_head_current_mode,
.position = output_head_position,
.transform = output_head_transform,
.scale = output_head_scale,
.finished = output_head_finished,
};
/* config object */
static void output_manager_config_succeeded(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request succeeded");
zwlr_output_configuration_v1_destroy(config);
}
static void output_manager_config_failed(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request failed");
zwlr_output_configuration_v1_destroy(config);
}
static void output_manager_config_cancelled(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request cancelled");
zwlr_output_configuration_v1_destroy(config);
}
struct zwlr_output_configuration_v1_listener wlr_output_config_listener = {
.succeeded = output_manager_config_succeeded,
.failed = output_manager_config_failed,
.cancelled = output_manager_config_cancelled,
};
/* manager itself */
static void output_manager_done(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
uint32_t serial)
{
last_config_serial = serial;
nvnc_trace("Got new serial: %u", serial);
}
static void output_manager_finished(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1)
{
nvnc_trace("output-manager destroyed");
wlr_output_manager = NULL;
}
static void output_manager_head(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
struct zwlr_output_head_v1* output_head)
{
struct output_manager_head* head = calloc(1, sizeof(*head));
if (!head) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
return;
}
head->head = output_head;
wl_list_insert(heads.prev, &head->link);
nvnc_trace("New head, now at %lu", wl_list_length(&heads));
zwlr_output_head_v1_add_listener(head->head,
&wlr_output_head_listener, head);
}
static const struct zwlr_output_manager_v1_listener
wlr_output_manager_listener = {
.head = output_manager_head,
.done = output_manager_done,
.finished = output_manager_finished,
};
/* Public API */
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager)
{
if (wlr_output_manager)
return;
wl_list_init(&heads);
wlr_output_manager = output_manager;
zwlr_output_manager_v1_add_listener(wlr_output_manager,
&wlr_output_manager_listener, NULL);
}
void wlr_output_manager_destroy(void)
{
if (!wlr_output_manager)
return;
struct output_manager_head* head;
struct output_manager_head* tmp;
wl_list_for_each_safe(head, tmp, &heads, link) {
wl_list_remove(&head->link);
free(head->name);
free(head);
}
zwlr_output_manager_v1_destroy(wlr_output_manager);
wlr_output_manager = NULL;
last_config_serial = 0;
}
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height)
{
if (!wlr_output_manager) {
nvnc_log(NVNC_LOG_INFO,
"output-management protocol not available, not resizing output");
return false;
}
if (!output->is_headless) {
nvnc_log(NVNC_LOG_INFO,
"not resizing output %s: not a headless one",
output->name);
return false;
}
// TODO: This could be synced to --max-fps
int refresh_rate = 0;
struct zwlr_output_configuration_v1* config;
struct zwlr_output_configuration_head_v1* config_head;
config = zwlr_output_manager_v1_create_configuration(
wlr_output_manager, last_config_serial);
zwlr_output_configuration_v1_add_listener(config,
&wlr_output_config_listener, NULL);
struct output_manager_head* head;
wl_list_for_each(head, &heads, link) {
if (!head->enabled) {
nvnc_trace("disabling output %s", head->name);
zwlr_output_configuration_v1_disable_head(
config, head->head);
continue;
}
config_head = zwlr_output_configuration_v1_enable_head(
config, head->head);
if (head->name && strcmp(head->name, output->name) == 0) {
nvnc_trace("reconfiguring output %s", head->name);
zwlr_output_configuration_head_v1_set_custom_mode(
config_head, width, height, refresh_rate);
/* It doesn't make any sense to have rotation on a
* headless display, so we set the transform here to be
* sure.
*/
zwlr_output_configuration_head_v1_set_transform(
config_head, WL_OUTPUT_TRANSFORM_NORMAL);
}
zwlr_output_configuration_head_v1_destroy(config_head);
}
nvnc_trace("applying new output config");
zwlr_output_configuration_v1_apply(config);
return true;
}
wayvnc-0.9.1/src/output.c 0000664 0000000 0000000 00000025243 14717463620 0015343 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.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "output.h"
#include "strlcpy.h"
#include "xdg-output-unstable-v1.h"
#include "wlr-output-power-management-unstable-v1.h"
extern struct zxdg_output_manager_v1* xdg_output_manager;
extern struct zwlr_output_power_manager_v1* wlr_output_power_manager;
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
void output_transform_coord(const struct output* self,
uint32_t src_x, uint32_t src_y,
uint32_t* dst_x, uint32_t* dst_y)
{
switch (self->transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
*dst_x = src_x;
*dst_y = src_y;
break;
case WL_OUTPUT_TRANSFORM_90:
*dst_x = src_y;
*dst_y = self->height - src_x;
break;
case WL_OUTPUT_TRANSFORM_180:
*dst_x = self->width - src_x;
*dst_y = self->height - src_y;
break;
case WL_OUTPUT_TRANSFORM_270:
*dst_x = self->width - src_y;
*dst_y = src_x;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED:
*dst_x = self->width - src_x;
*dst_y = src_y;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
*dst_x = src_y;
*dst_y = src_x;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
*dst_x = src_x;
*dst_y = self->height - src_y;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
*dst_x = self->width - src_y;
*dst_y = self->height - src_x;
break;
}
}
void output_transform_box_coord(const struct output* self,
uint32_t src_x0, uint32_t src_y0,
uint32_t src_x1, uint32_t src_y1,
uint32_t* dst_x0, uint32_t* dst_y0,
uint32_t* dst_x1, uint32_t* dst_y1)
{
uint32_t x0 = 0, y0 = 0, x1 = 0, y1 = 0;
output_transform_coord(self, src_x0, src_y0, &x0, &y0);
output_transform_coord(self, src_x1, src_y1, &x1, &y1);
*dst_x0 = MIN(x0, x1);
*dst_x1 = MAX(x0, x1);
*dst_y0 = MIN(y0, y1);
*dst_y1 = MAX(y0, y1);
}
static bool is_transform_90_degrees(enum wl_output_transform transform)
{
switch (transform) {
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_270:
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
return true;
default:
break;
}
return false;
}
uint32_t output_get_transformed_width(const struct output* self)
{
return is_transform_90_degrees(self->transform)
? self->height : self->width;
}
uint32_t output_get_transformed_height(const struct output* self)
{
return is_transform_90_degrees(self->transform)
? self->width : self->height;
}
static void output_handle_geometry(void* data, struct wl_output* wl_output,
int32_t x, int32_t y, int32_t phys_width,
int32_t phys_height, int32_t subpixel,
const char* make, const char* model,
int32_t transform)
{
struct output* output = data;
if (transform != (int32_t)output->transform)
output->is_transform_changed = true;
output->x = x;
output->y = y;
output->transform = transform;
strlcpy(output->make, make, sizeof(output->make));
strlcpy(output->model, model, sizeof(output->model));
}
static void output_handle_mode(void* data, struct wl_output* wl_output,
uint32_t flags, int32_t width, int32_t height,
int32_t refresh)
{
struct output* output = data;
if (!(flags & WL_OUTPUT_MODE_CURRENT))
return;
if (width != (int32_t)output->width || height != (int32_t)output->height)
output->is_dimension_changed = true;
output->width = width;
output->height = height;
}
static void output_handle_done(void* data, struct wl_output* wl_output)
{
struct output* output = data;
if (output->is_dimension_changed && output->on_dimension_change)
output->on_dimension_change(output);
if (output->is_transform_changed && output->on_transform_change)
output->on_transform_change(output);
output->is_dimension_changed = false;
output->is_transform_changed = false;
}
static void output_handle_scale(void* data, struct wl_output* wl_output,
int32_t factor)
{
}
static const struct wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = output_handle_mode,
.done = output_handle_done,
.scale = output_handle_scale,
};
void output_destroy(struct output* output)
{
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);
free(output);
}
void output_list_destroy(struct wl_list* list)
{
struct output* output;
struct output* tmp;
wl_list_for_each_safe(output, tmp, list, link) {
wl_list_remove(&output->link);
output_destroy(output);
}
}
void output_logical_position(void* data, struct zxdg_output_v1* xdg_output,
int32_t x, int32_t y)
{
}
void output_logical_size(void* data, struct zxdg_output_v1* xdg_output,
int32_t width, int32_t height)
{
}
void output_name(void* data, struct zxdg_output_v1* xdg_output,
const char* name)
{
struct output* self = data;
strlcpy(self->name, name, sizeof(self->name));
self->is_headless =
(strncmp(name, "HEADLESS-", strlen("HEADLESS-")) == 0) ||
(strncmp(name, "NOOP-", strlen("NOOP-")) == 0);
nvnc_trace("Output %u name: %s, headless: %s", self->id, self->name,
self->is_headless ? "yes" : "no");
}
void output_description(void* data, struct zxdg_output_v1* xdg_output,
const char* description)
{
struct output* self = data;
strlcpy(self->description, description, sizeof(self->description));
nvnc_trace("Output %u description: %s", self->id, self->description);
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
.logical_position = output_logical_position,
.logical_size = output_logical_size,
.done = NULL, /* Deprecated */
.name = output_name,
.description = output_description,
};
static void output_setup_xdg_output_manager(struct output* self)
{
if (!xdg_output_manager || self->xdg_output)
return;
struct zxdg_output_v1* xdg_output =
zxdg_output_manager_v1_get_xdg_output(
xdg_output_manager, self->wl_output);
self->xdg_output = xdg_output;
zxdg_output_v1_add_listener(self->xdg_output, &xdg_output_listener,
self);
}
const char* output_power_state_name(enum output_power_state state)
{
switch(state) {
case OUTPUT_POWER_ON:
return "ON";
case OUTPUT_POWER_OFF:
return "OFF";
case OUTPUT_POWER_UNKNOWN:
return "UNKNOWN";
}
abort();
return NULL;
}
static void output_power_mode(void *data,
struct zwlr_output_power_v1 *zwlr_output_power_v1,
uint32_t mode)
{
struct output* self = data;
nvnc_trace("Output %s power state changed to %s", self->name,
(mode == ZWLR_OUTPUT_POWER_V1_MODE_ON) ? "ON" : "OFF");
enum output_power_state old = self->power;
switch (mode) {
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
self->power = OUTPUT_POWER_OFF;
break;
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
self->power = OUTPUT_POWER_ON;
break;
}
if (old != self->power && self->on_power_change)
self->on_power_change(self);
}
static void output_power_failed(void *data,
struct zwlr_output_power_v1 *zwlr_output_power_v1)
{
struct output* self = data;
nvnc_log(NVNC_LOG_WARNING, "Output %s power state failure", self->name);
self->power = OUTPUT_POWER_UNKNOWN;
zwlr_output_power_v1_destroy(self->wlr_output_power);
self->wlr_output_power = NULL;
}
static const struct zwlr_output_power_v1_listener wlr_output_power_listener = {
.mode = output_power_mode,
.failed = output_power_failed,
};
int output_acquire_power_on(struct output* output)
{
if (output->wlr_output_power)
return 1;
if (!wlr_output_power_manager)
return -1;
struct zwlr_output_power_v1* wlr_output_power =
zwlr_output_power_manager_v1_get_output_power(
wlr_output_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 = OUTPUT_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->name, name) == 0)
return output;
return NULL;
}
struct output* output_first(struct wl_list* list)
{
struct output* output;
wl_list_for_each(output, list, link)
return output;
return output;
}
struct output* output_cycle(const struct wl_list* list,
const struct output* current,
enum output_cycle_direction direction)
{
const struct wl_list* iter = current ? ¤t->link : list;
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
iter->next : iter->prev;
if (iter == list) {
if (wl_list_empty(list))
return NULL;
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
iter->next : iter->prev;
}
struct output* output;
return wl_container_of(iter, output, link);
}
void output_setup_xdg_output_managers(struct wl_list* list)
{
struct output* output;
wl_list_for_each(output, list, link) {
output_setup_xdg_output_manager(output);
}
}
struct output* output_new(struct wl_output* wl_output, uint32_t id)
{
struct output* output = calloc(1, sizeof(*output));
if (!output) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
return NULL;
}
output->wl_output = wl_output;
output->id = id;
output->power = OUTPUT_POWER_UNKNOWN;
wl_output_add_listener(output->wl_output, &output_listener,
output);
output_setup_xdg_output_manager(output);
return output;
}
wayvnc-0.9.1/src/pam_auth.c 0000664 0000000 0000000 00000004606 14717463620 0015601 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;
}
wayvnc-0.9.1/src/pixels.c 0000664 0000000 0000000 00000005034 14717463620 0015303 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;
}
wayvnc-0.9.1/src/pointer.c 0000664 0000000 0000000 00000006345 14717463620 0015465 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.
*/
#include
#include
#include
#include
#include "pointer.h"
#include "wlr-virtual-pointer-unstable-v1.h"
#include "time-util.h"
int pointer_init(struct pointer* self)
{
zwlr_virtual_pointer_v1_axis_source(self->pointer,
WL_POINTER_AXIS_SOURCE_WHEEL);
return 0;
}
void pointer_destroy(struct pointer* self)
{
zwlr_virtual_pointer_v1_destroy(self->pointer);
}
static void pointer_set_button_mask(struct pointer* self, uint32_t t,
enum nvnc_button_mask mask)
{
enum nvnc_button_mask diff = self->current_mask ^ mask;
if (diff & NVNC_BUTTON_LEFT)
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_LEFT,
!!(mask & NVNC_BUTTON_LEFT));
if (diff & NVNC_BUTTON_MIDDLE)
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_MIDDLE,
!!(mask & NVNC_BUTTON_MIDDLE));
if (diff & NVNC_BUTTON_RIGHT)
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_RIGHT,
!!(mask & NVNC_BUTTON_RIGHT));
int vaxis = WL_POINTER_AXIS_VERTICAL_SCROLL;
int haxis = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
/* I arrived at the magical value of 15 by connecting a mouse with a
* scroll wheel and viewing the output of wev.
*/
if ((diff & NVNC_SCROLL_UP) && !(mask & NVNC_SCROLL_UP))
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis,
wl_fixed_from_int(-15), -1);
if ((diff & NVNC_SCROLL_DOWN) && !(mask & NVNC_SCROLL_DOWN))
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis,
wl_fixed_from_int(15), 1);
if ((diff & NVNC_SCROLL_LEFT) && !(mask & NVNC_SCROLL_LEFT))
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis,
wl_fixed_from_int(-15), -1);
if ((diff & NVNC_SCROLL_RIGHT) && !(mask & NVNC_SCROLL_RIGHT))
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis,
wl_fixed_from_int(15), 1);
self->current_mask = mask;
}
void pointer_set(struct pointer* self, uint32_t x, uint32_t y,
enum nvnc_button_mask button_mask)
{
uint32_t t = gettime_ms();
if (x != self->current_x || y != self->current_y)
zwlr_virtual_pointer_v1_motion_absolute(self->pointer, t,
x, y,
self->output->width,
self->output->height);
self->current_x = x;
self->current_y = y;
if (button_mask != self->current_mask)
pointer_set_button_mask(self, t, button_mask);
zwlr_virtual_pointer_v1_frame(self->pointer);
}
wayvnc-0.9.1/src/screencopy-interface.c 0000664 0000000 0000000 00000004035 14717463620 0020107 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.
*/
#include "screencopy-interface.h"
#include
extern struct zwlr_screencopy_manager_v1* screencopy_manager;
extern struct ext_output_image_capture_source_manager_v1*
ext_output_image_capture_source_manager;
extern struct ext_image_copy_capture_manager_v1* ext_image_copy_capture_manager;
extern struct screencopy_impl wlr_screencopy_impl;
extern struct screencopy_impl ext_image_copy_capture_impl;
struct screencopy* screencopy_create(struct wl_output* output,
bool render_cursor)
{
if (ext_image_copy_capture_manager && ext_output_image_capture_source_manager)
return ext_image_copy_capture_impl.create(output, render_cursor);
if (screencopy_manager)
return wlr_screencopy_impl.create(output, render_cursor);
return NULL;
}
struct screencopy* screencopy_create_cursor(struct wl_output* output,
struct wl_seat* seat)
{
if (ext_image_copy_capture_manager && ext_output_image_capture_source_manager)
return ext_image_copy_capture_impl.create_cursor(output, 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);
}
wayvnc-0.9.1/src/screencopy.c 0000664 0000000 0000000 00000022156 14717463620 0016155 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wlr-screencopy-unstable-v1.h"
#include "buffer.h"
#include "shm.h"
#include "screencopy-interface.h"
#include "smooth.h"
#include "time-util.h"
#include "usdt.h"
#include "pixels.h"
#include "config.h"
#define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s
extern struct zwlr_screencopy_manager_v1* screencopy_manager;
enum wlr_screencopy_status {
WLR_SCREENCOPY_STOPPED = 0,
WLR_SCREENCOPY_IN_PROGRESS,
WLR_SCREENCOPY_FAILED,
WLR_SCREENCOPY_FATAL,
WLR_SCREENCOPY_DONE,
};
struct wlr_screencopy {
struct screencopy parent;
enum wlr_screencopy_status status;
struct wv_buffer_pool* pool;
struct wv_buffer* front;
struct wv_buffer* back;
struct zwlr_screencopy_frame_v1* frame;
uint64_t last_time;
uint64_t start_time;
struct aml_timer* timer;
struct smooth delay_smoother;
double delay;
bool is_immediate_copy;
bool overlay_cursor;
struct wl_output* wl_output;
uint32_t wl_shm_width, wl_shm_height, wl_shm_stride;
enum wl_shm_format wl_shm_format;
bool have_linux_dmabuf;
uint32_t dmabuf_width, dmabuf_height;
uint32_t fourcc;
};
struct screencopy_impl wlr_screencopy_impl;
static void screencopy__stop(struct wlr_screencopy* self)
{
aml_stop(aml_get_default(), self->timer);
self->status = WLR_SCREENCOPY_STOPPED;
if (self->frame) {
zwlr_screencopy_frame_v1_destroy(self->frame);
self->frame = NULL;
}
}
void wlr_screencopy_stop(struct screencopy* ptr)
{
struct wlr_screencopy* self = (struct wlr_screencopy*)ptr;
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->front = NULL;
return screencopy__stop(self);
}
static void screencopy_linux_dmabuf(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t format, uint32_t width, uint32_t height)
{
#ifdef ENABLE_SCREENCOPY_DMABUF
struct wlr_screencopy* self = data;
if (!(wv_buffer_get_available_types() & WV_BUFFER_DMABUF))
return;
self->have_linux_dmabuf = true;
self->dmabuf_width = width;
self->dmabuf_height = height;
self->fourcc = format;
#endif
}
static void screencopy_buffer_done(void* data,
struct zwlr_screencopy_frame_v1* frame)
{
struct wlr_screencopy* self = data;
struct wv_buffer_config config = {};
#ifdef ENABLE_SCREENCOPY_DMABUF
if (self->have_linux_dmabuf && self->parent.enable_linux_dmabuf) {
config.width = self->dmabuf_width;
config.height = self->dmabuf_height;
config.stride = 0;
config.format = self->fourcc;
config.type = WV_BUFFER_DMABUF;
} else
#endif
{
config.width = self->wl_shm_width;
config.height = self->wl_shm_height;
config.stride = self->wl_shm_stride;
config.format = fourcc_from_wl_shm(self->wl_shm_format);
config.type = WV_BUFFER_SHM;
}
wv_buffer_pool_reconfig(self->pool, &config);
struct wv_buffer* buffer = wv_buffer_pool_acquire(self->pool);
if (!buffer) {
screencopy__stop(self);
self->status = WLR_SCREENCOPY_FATAL;
self->parent.on_done(SCREENCOPY_FATAL, NULL, self->parent.userdata);
return;
}
assert(!self->front);
self->front = buffer;
if (self->is_immediate_copy)
zwlr_screencopy_frame_v1_copy(self->frame, buffer->wl_buffer);
else
zwlr_screencopy_frame_v1_copy_with_damage(self->frame,
buffer->wl_buffer);
}
static void screencopy_buffer(void* data,
struct zwlr_screencopy_frame_v1* frame,
enum wl_shm_format format, uint32_t width,
uint32_t height, uint32_t stride)
{
struct wlr_screencopy* self = data;
self->wl_shm_format = format;
self->wl_shm_width = width;
self->wl_shm_height = height;
self->wl_shm_stride = stride;
int version = zwlr_screencopy_manager_v1_get_version(screencopy_manager);
if (version < 3) {
self->have_linux_dmabuf = false;
screencopy_buffer_done(data, frame);
return;
}
}
static void screencopy_flags(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t flags)
{
(void)frame;
struct wlr_screencopy* self = data;
self->front->y_inverted =
!!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
}
static void screencopy_ready(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec)
{
(void)sec_hi;
(void)sec_lo;
(void)nsec;
struct wlr_screencopy* self = data;
uint64_t sec = (uint64_t)sec_hi << 32 | (uint64_t)sec_lo;
uint64_t pts = sec * UINT64_C(1000000) + (uint64_t)nsec / UINT64_C(1000);
DTRACE_PROBE2(wayvnc, screencopy_ready, self, pts);
screencopy__stop(self);
self->last_time = gettime_us();
double delay = (self->last_time - self->start_time) * 1.0e-6;
self->delay = smooth(&self->delay_smoother, delay);
if (self->is_immediate_copy)
wv_buffer_damage_whole(self->front);
if (self->back)
wv_buffer_pool_release(self->pool, self->back);
self->back = self->front;
self->front = NULL;
nvnc_fb_set_pts(self->back->nvnc_fb, pts);
self->status = WLR_SCREENCOPY_DONE;
self->parent.on_done(SCREENCOPY_DONE, self->back, self->parent.userdata);
self->back = NULL;
}
static void screencopy_failed(void* data,
struct zwlr_screencopy_frame_v1* frame)
{
struct wlr_screencopy* self = data;
DTRACE_PROBE1(wayvnc, screencopy_failed, self);
screencopy__stop(self);
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->front = NULL;
self->status = WLR_SCREENCOPY_FAILED;
self->parent.on_done(SCREENCOPY_FAILED, NULL, self->parent.userdata);
}
static void screencopy_damage(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height)
{
struct wlr_screencopy* self = data;
DTRACE_PROBE1(wayvnc, screencopy_damage, self);
wv_buffer_damage_rect(self->front, x, y, width, height);
}
static int screencopy__start_capture(struct wlr_screencopy* self)
{
DTRACE_PROBE1(wayvnc, screencopy_start, self);
static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
.buffer = screencopy_buffer,
.linux_dmabuf = screencopy_linux_dmabuf,
.buffer_done = screencopy_buffer_done,
.flags = screencopy_flags,
.ready = screencopy_ready,
.failed = screencopy_failed,
.damage = screencopy_damage,
};
self->start_time = gettime_us();
self->frame = zwlr_screencopy_manager_v1_capture_output(
screencopy_manager, self->overlay_cursor,
self->wl_output);
if (!self->frame)
return -1;
zwlr_screencopy_frame_v1_add_listener(self->frame, &frame_listener,
self);
return 0;
}
static void screencopy__poll(void* obj)
{
struct wlr_screencopy* self = aml_get_userdata(obj);
screencopy__start_capture(self);
}
static int wlr_screencopy_start(struct screencopy* ptr, bool is_immediate_copy)
{
struct wlr_screencopy* self = (struct wlr_screencopy*)ptr;
if (self->status == WLR_SCREENCOPY_IN_PROGRESS)
return -1;
self->is_immediate_copy = is_immediate_copy;
uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6;
int32_t time_left = (1.0 / ptr->rate_limit - dt - self->delay) * 1.0e6;
self->status = WLR_SCREENCOPY_IN_PROGRESS;
if (time_left > 0) {
aml_set_duration(self->timer, time_left);
return aml_start(aml_get_default(), self->timer);
}
return screencopy__start_capture(self);
}
static struct screencopy* wlr_screencopy_create(struct wl_output* output,
bool render_cursor)
{
struct wlr_screencopy* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->parent.impl = &wlr_screencopy_impl;
self->parent.rate_limit = 30;
self->wl_output = output;
self->overlay_cursor = render_cursor;
self->pool = wv_buffer_pool_create(NULL);
assert(self->pool);
self->timer = aml_timer_new(0, screencopy__poll, self, NULL);
assert(self->timer);
self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT;
return (struct screencopy*)self;
}
static void wlr_screencopy_destroy(struct screencopy* ptr)
{
struct wlr_screencopy* self = (struct wlr_screencopy*)ptr;
aml_stop(aml_get_default(), self->timer);
aml_unref(self->timer);
if (self->back)
wv_buffer_pool_release(self->pool, self->back);
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->back = NULL;
self->front = NULL;
wv_buffer_pool_destroy(self->pool);
free(self);
}
struct screencopy_impl wlr_screencopy_impl = {
.caps = 0,
.create = wlr_screencopy_create,
.destroy = wlr_screencopy_destroy,
.start = wlr_screencopy_start,
.stop = wlr_screencopy_stop,
};
wayvnc-0.9.1/src/seat.c 0000664 0000000 0000000 00000005135 14717463620 0014735 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.
*/
#include
#include
#include
#include
#include
#include
#include "seat.h"
#include "strlcpy.h"
static void seat_capabilities(void* data, struct wl_seat* wl_seat,
uint32_t capabilities)
{
struct seat* self = data;
self->capabilities = capabilities;
}
static void seat_name(void* data, struct wl_seat* wl_seat, const char* name)
{
struct seat* self = data;
strlcpy(self->name, name, sizeof(self->name));
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_capabilities,
.name = seat_name,
};
struct seat* seat_new(struct wl_seat* wl_seat, uint32_t id)
{
struct seat* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->wl_seat = wl_seat;
self->id = id;
wl_seat_add_listener(wl_seat, &seat_listener, self);
return self;
}
void seat_destroy(struct seat* self)
{
wl_seat_destroy(self->wl_seat);
free(self);
}
void seat_list_destroy(struct wl_list* list)
{
struct seat* seat;
struct seat* tmp;
wl_list_for_each_safe(seat, tmp, list, link) {
wl_list_remove(&seat->link);
seat_destroy(seat);
}
}
struct seat* seat_find_by_name(struct wl_list* list, const char* name)
{
struct seat* seat;
wl_list_for_each(seat, list, link)
if (strcmp(seat->name, name) == 0)
return seat;
return NULL;
}
struct seat* seat_find_by_id(struct wl_list* list, uint32_t id)
{
struct seat* seat;
wl_list_for_each(seat, list, link)
if (seat->id == id)
return seat;
return NULL;
}
struct seat* seat_find_unoccupied(struct wl_list* list)
{
struct seat* seat;
wl_list_for_each(seat, list, link)
if (seat->occupancy == 0)
return seat;
return NULL;
}
struct seat* seat_first(struct wl_list* list)
{
struct seat* seat;
wl_list_for_each(seat, list, link)
return seat;
return NULL;
}
wayvnc-0.9.1/src/shm.c 0000664 0000000 0000000 00000004227 14717463620 0014571 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.
*/
#include
#include
#include
#include
#include
#include "config.h"
// Linux with glibc < 2.27 has no wrapper
#if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE)
#include
static inline int memfd_create(const char *name, unsigned int flags) {
return syscall(SYS_memfd_create, name, flags);
}
#endif
#if !defined(HAVE_MEMFD) && !defined(__FreeBSD__)
static void randname(char *buf)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
buf[i] = 'A'+(r&15)+(r&16)*2;
r >>= 5;
}
}
#endif
static int create_shm_file(void)
{
#ifdef HAVE_MEMFD
return memfd_create("wayvnc-shm", 0);
#elif defined(__FreeBSD__)
// memfd_create added in FreeBSD 13, but SHM_ANON has been supported for ages
return shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600);
#else
int retries = 100;
do {
char name[] = "/wl_shm-XXXXXX";
randname(name + sizeof(name) - 7);
--retries;
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
#endif
}
int shm_alloc_fd(size_t size)
{
int fd = create_shm_file();
if (fd < 0)
return -1;
int ret;
do {
ret = ftruncate(fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
close(fd);
return -1;
}
return fd;
}
wayvnc-0.9.1/src/smooth.c 0000664 0000000 0000000 00000002220 14717463620 0015302 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.
*/
#include "smooth.h"
#include "time-util.h"
#include
double smooth(struct smooth* self, double input)
{
uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6;
self->last_time = now;
double factor = 1.0 - exp(-dt / self->time_constant);
double result = factor * input + (1.0 - factor) * self->last_result;
self->last_result = result;
return result;
}
wayvnc-0.9.1/src/strlcpy.c 0000664 0000000 0000000 00000003100 14717463620 0015467 0 ustar 00root root 0000000 0000000 /* $OpenBSD: strlcpy.c,v 1.13 2015/08/31 02:53:57 guenther Exp $ */
/*
* Copyright (c) 1998, 2015 Todd C. Miller
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include
#include
/*
* Copy string src to buffer dst of size dsize. At most dsize-1
* chars will be copied. Always NUL terminates (unless dsize == 0).
* Returns strlen(src); if retval >= dsize, truncation occurred.
*/
size_t
strlcpy(char *dst, const char *src, size_t dsize)
{
const char *osrc = src;
size_t nleft = dsize;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == 0) {
if (dsize != 0)
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return(src - osrc - 1); /* count does not include NUL */
}
wayvnc-0.9.1/src/table-printer.c 0000664 0000000 0000000 00000006630 14717463620 0016552 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2023 Andri Yngvason
* Copyright (c) 2023 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "table-printer.h"
#include
#include
#include
#include
#include
static struct table_printer defaults = {
.max_width = 80,
.left_indent = 4,
.column_offset = 4,
.stream = NULL,
.left_width = 30,
};
void table_printer_set_defaults(int max_width, int left_indent,
int column_offset)
{
defaults.max_width = max_width;
defaults.left_indent = left_indent;
defaults.column_offset = column_offset;
}
void table_printer_init(struct table_printer* self, FILE* stream)
{
memcpy(self, &defaults, sizeof(*self));
self->stream = stream;
}
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
int width)
{
int line_len = 0;
int last_space_pos = 0;
int dst_len = 0;
int i = 0;
while (true) {
char c = src[i];
if (line_len > width) {
// first word > width
assert(last_space_pos > 0);
// subsequent word > width
assert(dst[last_space_pos] != '\n');
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
}
if (!c)
break;
if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
assert(dst_len < dst_size);
++line_len;
++i;
if (c == '\n')
line_len = 0;
}
dst[dst_len] = '\0';
return dst_len;
}
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
int width, int first_line_indent, int subsequent_indent)
{
char buffer[256];
table_printer_reflow_text(buffer, sizeof(buffer), src, width);
char* line = strtok(buffer, "\n");
fprintf(stream, "%*s%s\n", first_line_indent, "", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", subsequent_indent, "", line);
}
}
void table_printer_print_line(struct table_printer* self, const char* left_text,
const char* right_text)
{
int field_len = fprintf(self->stream, "%*s", self->left_indent, "");
field_len += fprintf(self->stream, "%s", left_text);
if (field_len > self->left_width - self->column_offset -
self->left_indent) {
fprintf(self->stream, "\n");
field_len = 0;
}
int column_width = self->max_width - self->left_width;
int first_indent = self->left_width - field_len;
int subsequent_indent = self->left_width;
table_printer_indent_and_reflow_text(self->stream, right_text,
column_width, first_indent, subsequent_indent);
}
void table_printer_print_fmtline(struct table_printer* self,
const char* right_text,
const char* left_format, ...)
{
char buf[64];
va_list args;
va_start(args, left_format);
vsnprintf(buf, sizeof(buf), left_format, args);
va_end(args);
table_printer_print_line(self, buf, right_text);
}
wayvnc-0.9.1/src/transform-util.c 0000664 0000000 0000000 00000015236 14717463620 0016772 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.
*
* For code borrowed from wlroots:
* Copyright (c) 2017, 2018 Drew DeVault
* Copyright (c) 2014 Jari Vetoniemi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include
#include
#include
/* Note: This function yields the inverse pixman transform of the
* wl_output_transform.
*/
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
enum wl_output_transform src, int width, int height)
{
#define F1 pixman_fixed_1
switch (src) {
case WL_OUTPUT_TRANSFORM_NORMAL:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_180:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
}
#undef F1
abort();
}
/* Borrowed these from wlroots */
void wv_region_transform(struct pixman_region16* dst,
struct pixman_region16* src, enum wl_output_transform transform,
int width, int height)
{
if (transform == WL_OUTPUT_TRANSFORM_NORMAL) {
pixman_region_copy(dst, src);
return;
}
int nrects = 0;
pixman_box16_t* src_rects = pixman_region_rectangles(src, &nrects);
pixman_box16_t* dst_rects = malloc(nrects * sizeof(*dst_rects));
if (dst_rects == NULL) {
return;
}
for (int i = 0; i < nrects; ++i) {
switch (transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = src_rects[i].y2;
break;
case WL_OUTPUT_TRANSFORM_90:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = src_rects[i].x2;
break;
case WL_OUTPUT_TRANSFORM_180:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case WL_OUTPUT_TRANSFORM_270:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = src_rects[i].y2;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = src_rects[i].x2;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
}
}
pixman_region_fini(dst);
pixman_region_init_rects(dst, dst_rects, nrects);
free(dst_rects);
}
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr)
{
if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
tr ^= WL_OUTPUT_TRANSFORM_180;
}
return tr;
}
enum wl_output_transform wv_output_transform_compose(
enum wl_output_transform tr_a, enum wl_output_transform tr_b)
{
uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED;
uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180;
uint32_t rotated;
if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) {
// When a rotation of k degrees is followed by a flip, the
// equivalent transform is a flip followed by a rotation of
// -k degrees.
rotated = (tr_b - tr_a) & rotation_mask;
} else {
rotated = (tr_a + tr_b) & rotation_mask;
}
return flipped | rotated;
}
wayvnc-0.9.1/src/util.c 0000664 0000000 0000000 00000003072 14717463620 0014754 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2019 - 2022 Andri Yngvason
* Copyright (c) 2022 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include
#include