Compare commits

..

2 Commits

Author SHA1 Message Date
Andri Yngvason f0446aabbf Do tiled damage check in shader 2020-03-29 18:47:24 +00:00
Andri Yngvason 96e8c63042 renderer: Pass frame dimmensions to damage shader 2020-03-29 14:29:06 +00:00
94 changed files with 2364 additions and 11909 deletions

View File

@ -1,45 +0,0 @@
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

View File

@ -1,50 +0,0 @@
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

View File

@ -1,28 +0,0 @@
---
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

View File

@ -1,5 +0,0 @@
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"

View File

@ -1,6 +0,0 @@
---
name: Enhancements
about: New functionality
labels: 'enhancement'
---

View File

@ -1 +0,0 @@
Please read CONTRIBUTING.md before making a pull request.

View File

@ -1,38 +0,0 @@
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@v3
- name: checkout aml
uses: actions/checkout@v3
with:
repository: any1/aml
path: subprojects/aml
- name: checkout neatvnc
uses: actions/checkout@v3
with:
repository: any1/neatvnc
path: subprojects/neatvnc
- name: prepare environment
run: |
sudo apt-get update
sudo apt-get install -y meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev libavutil-dev libturbojpeg0-dev scdoc
# runtime deps for integration testing:
sudo apt-get install -y sway jq lsof
pip install vncdotool
- name: configure
run: meson build -D tests=true
- name: compile
run: meson compile -C build
- name: unit tests
run: meson test --verbose -C build
- name: integration tests
run: ./test/integration/integration.sh

4
.gitignore vendored
View File

@ -4,7 +4,3 @@ subprojects
.clang_complete
.ycm_extra_conf.py
perf.*
*.pem
.vimrc
.cache
.vscode

View File

@ -1,185 +0,0 @@
# 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.

26
FAQ.md
View File

@ -3,9 +3,7 @@
**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.
`WLR_LIBINPUT_NO_DEVICES=1` before starting sway, then run wayvnc as normal.
**Q: How can I pass my mod-key from Sway to the remote desktop session?**
@ -19,15 +17,23 @@ 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:
**Q: Wayvnc changes Sway's keyboard layout. Can this be fixed?**
A: This happens because of a bug in how the virtual keyboard protocol interacts
with a feature in sway called smart keyboard grouping. This can be remedied by
either putting wayvnc on a separate seat or turning off smart keyboard grouping
in sway.
Putting wayvnc in its own seat is done like this:
```
mode passthrough {
bindsym $mod+Pause mode default; floating_modifier $mod normal
}
bindsym $mod+Pause mode passthrough; floating_modifier none
swaymsg seat wayvnc fallback false # create a new seat
wayvnc --seat=wayvnc
```
Smart keyboard grouping can be turned off like this:
```
swaymsg seat seat0 keyboard_grouping 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?**

View File

@ -1,2 +0,0 @@
github: any1
patreon: andriyngvason

144
README.md
View File

@ -1,31 +1,36 @@
# wayvnc
[![Build and Unit Test](https://github.com/any1/wayvnc/actions/workflows/build.yml/badge.svg)](https://github.com/any1/wayvnc/actions/workflows/build.yml)
[![builds.sr.ht status](https://builds.sr.ht/~andri/wayvnc/pulls/1.svg)](https://builds.sr.ht/~andri/wayvnc/pulls/1?)
[![Packaging status](https://repology.org/badge/tiny-repos/wayvnc.svg)](https://repology.org/project/wayvnc/versions)
## Introduction
This is a VNC server for wlroots-based Wayland compositors (:no_entry: Gnome,
KDE and Weston are **not** supported). It attaches to a running Wayland session,
creates virtual input devices, and exposes a single display via the RFB
protocol. The Wayland session may be a headless one, so it is also possible
to run wayvnc without a physical display attached.
This is a VNC server for wlroots based Wayland compositors. It attaches to a
running Wayland session, creates virtual input devices and exposes a single
display via the RFB protocol. The Wayland session may be a headless one, so it
is also possible to run wayvnc without a physical display attached.
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.
support, join the #wayvnc IRC channel on freenode.
## Installing
### archlinux
```
git clone https://aur.archlinux.org/neatvnc.git
cd neatvnc
makepkg -si
cd -
git clone https://aur.archlinux.org/wayvnc.git
cd wayvnc
makepkg -si
cd -
```
## Building
### Runtime Dependencies
* aml
* drm
* gbm (optional)
* EGL
* libxkbcommon
* neatvnc
* pam (optional)
* OpenGL ES V2.0
* pixman
* jansson
### Build Dependencies
* GCC
@ -33,36 +38,20 @@ project.
* ninja
* pkg-config
#### For Arch Linux
#### For archlinux
```
pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson
pacman -S base-devel libglvnd libxkbcommon pixman gnutls
```
#### For Fedora 37
#### For fedora 31
```
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
libxkbcommon-devel libxkbcommon libwayland-client libwayland \
wayland-devel gnutls-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:
```
@ -82,22 +71,13 @@ ln -s ../../aml .
cd -
```
### Configure and Build
Setting the buildtype flag is not required but it is recommended as there are
significant performance gains to be had from an optimised build.
```
meson build
meson build --buildtype=release
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:
```
@ -113,20 +93,15 @@ accept connections via any interface, set the address to `0.0.0.0` like this:
: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.
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:
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 \
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout key.pem -out 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
@ -136,56 +111,13 @@ 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
private_key_file=/path/to/key.pem
certificate_file=/path/to/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.
## Compatible Software
See https://github.com/any1/neatvnc#client-compatibility

View File

@ -1,26 +0,0 @@
# 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.

View File

@ -1,88 +0,0 @@
#!/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())

View File

@ -1,49 +0,0 @@
#!/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 <http://unlicense.org/>
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)

View File

@ -1,124 +0,0 @@
#!/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 <http://unlicense.org/>
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)

View File

@ -1,85 +0,0 @@
/*
* 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 <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <pixman.h>
struct wl_buffer;
struct gbm_bo;
struct nvnc_fb;
enum wv_buffer_type {
WV_BUFFER_UNSPEC = 0,
WV_BUFFER_SHM,
#ifdef ENABLE_SCREENCOPY_DMABUF
WV_BUFFER_DMABUF,
#endif
};
struct wv_buffer {
enum wv_buffer_type type;
TAILQ_ENTRY(wv_buffer) link;
struct nvnc_fb* nvnc_fb;
struct wl_buffer* wl_buffer;
void* pixels;
size_t size;
int width, height, stride;
uint32_t format;
bool y_inverted;
struct pixman_region16 damage;
/* The following is only applicable to DMABUF */
struct gbm_bo* bo;
};
TAILQ_HEAD(wv_buffer_queue, wv_buffer);
struct wv_buffer_pool {
struct wv_buffer_queue queue;
enum wv_buffer_type type;
int width, height, stride;
uint32_t format;
};
enum wv_buffer_type wv_buffer_get_available_types(void);
struct wv_buffer* wv_buffer_create(enum wv_buffer_type, int width, int height,
int stride, uint32_t fourcc);
void wv_buffer_destroy(struct wv_buffer* self);
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height);
void wv_buffer_damage_whole(struct wv_buffer* self);
void wv_buffer_damage_clear(struct wv_buffer* self);
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type, int width,
int height, int stride, uint32_t format);
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool);
void wv_buffer_pool_resize(struct wv_buffer_pool* pool, enum wv_buffer_type,
int width, int height, int stride, uint32_t format);
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool);
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 - 2023 Andri Yngvason
* 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
@ -21,24 +21,14 @@
#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;

View File

@ -1,39 +0,0 @@
/*
* 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 <stdio.h>
#include <stdbool.h>
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);

View File

@ -1,71 +0,0 @@
/*
* 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 <stdbool.h>
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[];

View File

@ -1,94 +0,0 @@
/*
* 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 <sys/socket.h>
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);

View File

@ -16,13 +16,19 @@
#pragma once
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(a, b) ((b) * UDIV_UP((a), (b)))
struct pixman_region16;
struct pixman_box16;
extern const char* wayvnc_version;
void damage_check(struct pixman_region16* damage, const uint8_t* buffer,
uint32_t width, uint32_t height, struct pixman_box16* hint);
const char* default_ctl_socket_path();
int damage_check_async(uint8_t* buffer, uint32_t width, uint32_t height,
struct pixman_box16* hint,
void (*on_done)(struct pixman_region16*, void*),
void* userdata);
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by);
void damage_dump(FILE* stream, struct pixman_region16* damage,
uint32_t width, uint32_t height, uint32_t tile_size);

View File

@ -1,38 +0,0 @@
/*
* 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 <neatvnc.h>
#include "wlr-data-control-unstable-v1.h"
struct data_control {
struct wl_display* wl_display;
struct nvnc* server;
struct zwlr_data_control_manager_v1* manager;
struct zwlr_data_control_device_v1* device;
struct zwlr_data_control_source_v1* selection;
struct zwlr_data_control_source_v1* primary_selection;
struct zwlr_data_control_offer_v1* offer;
const char* mime_type;
char* cb_data;
size_t cb_len;
};
void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat);
void data_control_destroy(struct data_control* self);
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 The wayvnc authors
* 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
@ -18,11 +18,40 @@
#include <stdint.h>
#include <stdbool.h>
#include <aml.h>
#include "frame-capture.h"
struct output;
struct zwlr_output_manager_v1;
struct zwlr_export_dmabuf_manager_v1;
struct zwlr_export_dmabuf_frame_v1;
struct wl_output;
struct aml_timer;
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);
struct dmabuf_plane {
int fd;
uint32_t offset;
uint32_t size;
uint32_t pitch;
uint64_t modifier;
};
struct dmabuf_frame {
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t n_planes;
struct dmabuf_plane plane[4];
};
struct dmabuf_capture {
struct frame_capture fc;
struct zwlr_export_dmabuf_manager_v1* manager;
struct zwlr_export_dmabuf_frame_v1* zwlr_frame;
struct dmabuf_frame frame;
uint64_t last_time;
struct aml_timer* timer;
};
void dmabuf_capture_init(struct dmabuf_capture* self);

View File

@ -0,0 +1,80 @@
/*
* 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 <stdint.h>
#include <stdbool.h>
struct wl_output;
struct nvnc_fb;
struct renderer;
enum frame_capture_status {
CAPTURE_STOPPED = 0,
CAPTURE_IN_PROGRESS,
CAPTURE_FAILED,
CAPTURE_FATAL,
CAPTURE_DONE,
};
struct frame_capture {
enum frame_capture_status status;
bool overlay_cursor;
struct wl_output* wl_output;
void* userdata;
void (*on_done)(struct frame_capture*);
struct {
uint32_t fourcc_format;
uint32_t width;
uint32_t height;
uint32_t stride;
} frame_info;
struct {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
} damage_hint;
struct {
void (*render)(struct frame_capture*, struct renderer*,
struct nvnc_fb* fb);
int (*start)(struct frame_capture*);
void (*stop)(struct frame_capture*);
} backend;
};
static inline int frame_capture_start(struct frame_capture* self)
{
return self->backend.start(self);
}
static inline void frame_capture_stop(struct frame_capture* self)
{
self->backend.stop(self);
}
static inline void frame_capture_render(struct frame_capture* self,
struct renderer* renderer,
struct nvnc_fb* fb)
{
return self->backend.render(self, renderer, fb);
}

View File

@ -1,70 +0,0 @@
/*
* 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 <jansson.h>
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);

View File

@ -19,13 +19,11 @@
#include <stdlib.h>
#include <xkbcommon/xkbcommon.h>
#include <stdbool.h>
#include <neatvnc.h>
#include "intset.h"
struct zwp_virtual_keyboard_v1;
struct table_entry;
struct nvnc;
struct keyboard {
struct zwp_virtual_keyboard_v1* virtual_keyboard;
@ -41,9 +39,6 @@ struct keyboard {
struct intset key_state;
};
int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names);
int keyboard_init(struct keyboard* self, const char* layout);
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*);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Nicholas Sica
* 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
@ -16,6 +16,12 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
bool pam_auth(const char* username, const char* password);
#ifdef NDEBUG
#define log_debug(...)
#else
#define log_debug(...) fprintf(stderr, "DEBUG: " __VA_ARGS__)
#endif
#define log_error(...) fprintf(stderr, "ERROR: " __VA_ARGS__)

View File

@ -1,67 +0,0 @@
/*
* 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 <stdio.h>
#include <stdbool.h>
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);

View File

@ -16,27 +16,15 @@
#pragma once
#include <wayland-client-protocol.h>
#include <wayland-client.h>
#include <stdint.h>
#include <stdbool.h>
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;
@ -53,36 +41,17 @@ struct output {
char model[256];
char name[256];
char description[256];
enum output_power_state power;
bool is_dimension_changed;
bool is_transform_changed;
bool is_headless;
void (*on_dimension_change)(struct output*);
void (*on_transform_change)(struct output*);
void (*on_power_change)(struct output*);
void* userdata;
};
struct output* output_new(struct wl_output* wl_output, uint32_t id);
void output_destroy(struct output* output);
void output_setup_wl_managers(struct wl_list* list);
int output_set_power_state(struct output* output, enum output_power_state state);
void output_set_xdg_output(struct output* output,
struct zxdg_output_v1* xdg_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);

View File

@ -1,25 +0,0 @@
/*
* 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 <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <stdbool.h>
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);

84
include/render.h 100644
View File

@ -0,0 +1,84 @@
/*
* 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 <GLES2/gl2.h>
#include <EGL/egl.h>
#include <unistd.h>
#include <wayland-client.h>
struct dmabuf_frame;
struct output;
enum renderer_input_type {
RENDERER_INPUT_FB,
RENDERER_INPUT_DMABUF,
};
struct renderer_fbo {
GLuint rbo;
GLuint fbo;
GLuint tex;
};
struct renderer {
EGLDisplay display;
EGLContext context;
const struct output* output;
GLint read_format;
GLint read_type;
struct renderer_fbo frame_fbo[2];
int frame_index;
struct renderer_fbo damage_fbo;
struct {
GLuint program;
GLint u_tex0;
GLint u_proj;
} frame_shader;
struct {
GLuint program;
GLint u_tex0;
GLint u_tex1;
GLint u_width;
GLint u_height;
} damage_shader;
};
int renderer_init(struct renderer* self, const struct output* output,
enum renderer_input_type input_type);
void renderer_destroy(struct renderer* self);
int render_dmabuf(struct renderer* self, struct dmabuf_frame* frame);
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride);
/* Copy a horizontal stripe from the GL frame into a pixel buffer */
void renderer_read_frame(struct renderer* self, void* dst, uint32_t y,
uint32_t height);
void renderer_read_damage(struct renderer* self, void* dst, uint32_t y,
uint32_t height);
void renderer_swap_textures(struct renderer* self);
void render_damage(struct renderer* self);

View File

@ -19,8 +19,8 @@
#include <stdbool.h>
#include "wlr-screencopy-unstable-v1.h"
#include "frame-capture.h"
#include "smooth.h"
#include "buffer.h"
struct zwlr_screencopy_manager_v1;
struct zwlr_screencopy_frame_v1;
@ -31,51 +31,31 @@ struct aml_timer;
struct renderer;
enum screencopy_status {
SCREENCOPY_STOPPED = 0,
SCREENCOPY_IN_PROGRESS,
SCREENCOPY_FAILED,
SCREENCOPY_FATAL,
SCREENCOPY_DONE,
SCREENCOPY_STATUS_CAPTURING = 0,
SCREENCOPY_STATUS_FATAL,
SCREENCOPY_STATUS_FAILED,
SCREENCOPY_STATUS_DONE,
};
struct screencopy {
enum screencopy_status status;
struct frame_capture frame_capture;
struct wv_buffer_pool* pool;
struct wv_buffer* front;
struct wv_buffer* back;
struct wl_shm* wl_shm;
struct wl_buffer* buffer;
void* pixels;
size_t bufsize;
struct zwlr_screencopy_manager_v1* manager;
struct zwlr_screencopy_frame_v1* frame;
void* userdata;
void (*on_done)(struct screencopy*);
uint64_t last_time;
uint64_t start_time;
struct aml_timer* timer;
struct smooth delay_smoother;
double delay;
bool is_immediate_copy;
bool overlay_cursor;
struct wl_output* wl_output;
uint32_t wl_shm_width, wl_shm_height, wl_shm_stride;
enum wl_shm_format wl_shm_format;
bool have_linux_dmabuf;
bool enable_linux_dmabuf;
uint32_t dmabuf_width, dmabuf_height;
uint32_t fourcc;
double rate_limit;
};
void screencopy_init(struct screencopy* self);
void screencopy_destroy(struct screencopy* self);
int screencopy_start(struct screencopy* self);
int screencopy_start_immediate(struct screencopy* self);
void screencopy_stop(struct screencopy* self);

View File

@ -26,8 +26,6 @@ struct seat {
uint32_t id;
uint32_t capabilities;
char name[256];
uint32_t occupancy;
};
struct seat* seat_new(struct wl_seat* wl_seat, uint32_t id);
@ -36,5 +34,4 @@ 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);

View File

@ -1,21 +1,3 @@
/*
* 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 <unistd.h>
int shm_alloc_fd(size_t size);

View File

@ -1,19 +1,3 @@
/*
* 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 <unistd.h>

View File

@ -1,46 +0,0 @@
/*
* 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 <stdio.h>
struct table_printer{
FILE* stream;
int max_width;
int left_indent;
int left_width;
int column_offset;
};
// Sets default values for every subsequent table_printer_new (Optional: defaults to 80/4/8)
void table_printer_set_defaults(int max_width, int left_indent,
int column_offset);
void table_printer_init(struct table_printer* self, FILE* stream,
int left_width);
void table_printer_print_line(struct table_printer* self, const char* left_text,
const char* right_text);
void table_printer_print_fmtline(struct table_printer* self,
const char* right_text,
const char* left_format, ...);
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
int width);
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
int width, int first_line_indent, int subsequent_indent);

View File

@ -19,28 +19,16 @@
#include <time.h>
#include <stdint.h>
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);
return ts.tv_sec * 1000000ULL + (double)ts.tv_nsec / 1000ULL;
}
static inline uint64_t gettime_ms(void)
static inline uint32_t gettime_ms(void)
{
struct timespec ts = { 0 };
clock_gettime(CLOCK_MONOTONIC, &ts);
return timespec_to_ms(&ts);
return ts.tv_sec * 1000UL + ts.tv_nsec / 1000000UL;
}

View File

@ -1,31 +0,0 @@
/*
* 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 <wayland-client.h>
#include <pixman.h>
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);

View File

@ -1,145 +0,0 @@
/*
* 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 <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#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);

View File

@ -1,31 +0,0 @@
/*
* 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 <sys/sdt.h>
#else
#define DTRACE_PROBE(...)
#define DTRACE_PROBE1(...)
#define DTRACE_PROBE2(...)
#define DTRACE_PROBE3(...)
#define DTRACE_PROBE4(...)
#define DTRACE_PROBE5(...)
#define DTRACE_PROBE6(...)
#endif

View File

@ -1,44 +1,21 @@
project(
'wayvnc',
'c',
version: '0.9-dev',
version: '0.1.0',
license: 'ISC',
default_options: [
'c_std=gnu11',
'warning_level=2',
],
)
buildtype = get_option('buildtype')
host_system = host_machine.system()
prefix = get_option('prefix')
c_args = [
'-D_GNU_SOURCE',
'-DAML_UNSTABLE_API=1',
'-DWLR_USE_UNSTABLE=true',
'-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'
if buildtype == 'release' or buildtype == 'plain'
c_args += '-DNDEBUG'
endif
@ -48,71 +25,50 @@ 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_server = dependency('wayland-server')
egl = dependency('egl')
glesv2 = dependency('glesv2')
xkbcommon = dependency('xkbcommon')
wayland_client = dependency('wayland-client')
wayland_client_protocol = dependency('wayland-protocols')
wayland_cursor = dependency('wayland-cursor')
jansson = dependency('jansson')
# Cursor image
x11_dep = dependency('x11')
x11_fixes_dep = dependency('xfixes')
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)
aml_project = subproject('aml', required: false)
if aml_project.found()
aml = aml_project.get_variable('aml_dep')
else
aml = dependency('aml', version: aml_version)
aml = dependency('aml')
endif
if neatvnc_project.found()
neatvnc = neatvnc_project.get_variable('neatvnc_dep')
else
neatvnc = dependency('neatvnc', version: neatvnc_version)
neatvnc = dependency('neatvnc')
endif
inc = include_directories('include', '/usr/include/wlroots0.16')
inc = include_directories('include')
subdir('protocols')
sources = [
'src/main.c',
'src/render.c',
'src/dmabuf.c',
'src/strlcpy.c',
'src/shm.c',
'src/screencopy.c',
'src/data-control.c',
'src/output.c',
'src/output-management.c',
'src/pointer.c',
'src/keyboard.c',
'src/seat.c',
'src/smooth.c',
'src/cfg.c',
'src/intset.c',
'src/buffer.c',
'src/pixels.c',
'src/transform-util.c',
'src/util.c',
'src/json-ipc.c',
'src/ctl-server.c',
'src/ctl-commands.c',
'src/option-parser.c',
'src/table-printer.c',
'src/damage.c',
]
dependencies = [
@ -120,69 +76,25 @@ dependencies = [
librt,
pixman,
aml,
gbm,
drm,
egl,
glesv2,
wayland_client,
neatvnc,
xkbcommon,
client_protos,
jansson,
x11_dep,
x11_fixes_dep,
wayland_client_protocol,
wayland_cursor,
wayland_server
]
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 <sys/syscall.h>')
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,
)
install_subdir('shaders', install_dir: 'share/wayvnc')
executable(
'wayvnc',
sources,
@ -190,39 +102,3 @@ executable(
include_directories: inc,
install: true,
)
executable(
'wayvncctl',
ctlsources,
dependencies: ctldependencies,
include_directories: inc,
install: true,
)
scdoc = dependency('scdoc', native: true, required: get_option('man-pages'))
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
mandir = get_option('mandir')
manpages = {
'wayvnc.scd': 'wayvnc.1',
'wayvncctl.scd': 'wayvncctl.1',
}
foreach input, output : manpages
custom_target(
output,
input: input,
output: output,
command: [
sh, '-c', '@0@ <@INPUT@ >@1@'.format(scdoc_prog.path(), output)
],
install: true,
install_dir: '@0@/man1'.format(mandir)
)
endforeach
endif
if get_option('tests')
subdir('test')
endif

View File

@ -1,10 +0,0 @@
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')

View File

@ -1,110 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_transient_seat_v1">
<copyright>
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.
</copyright>
<description summary="protocol for creating temporary seats">
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
</description>
<interface name="ext_transient_seat_manager_v1" version="1">
<description summary="transient seat manager">
The transient seat manager creates short-lived seats.
</description>
<request name="create">
<description summary="create a transient seat">
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.
</description>
<arg name="seat" type="new_id" interface="ext_transient_seat_v1"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the manager.
All objects created by the manager will remain valid until they are
destroyed themselves.
</description>
</request>
</interface>
<interface name="ext_transient_seat_v1" version="1">
<description summary="transient seat handle">
When the transient seat handle is destroyed, the seat itself will also be
destroyed.
</description>
<event name="ready">
<description summary="transient seat is ready">
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.
</description>
<arg name="global_name" type="uint"/>
</event>
<event name="denied">
<description summary="transient seat creation denied">
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.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy transient seat">
When the transient seat object is destroyed by the client, the
associated seat created by the compositor is also destroyed.
</description>
</request>
</interface>
</protocol>

View File

@ -1,362 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="linux_dmabuf_unstable_v1">
<copyright>
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.
</copyright>
<interface name="zwp_linux_dmabuf_v1" version="3">
<description summary="factory for creating dmabuf-based wl_buffers">
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.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the factory">
Objects created through this interface, especially wl_buffers, will
remain valid.
</description>
</request>
<request name="create_params">
<description summary="create a temporary object for buffer parameters">
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.
</description>
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
summary="the new temporary"/>
</request>
<event name="format">
<description summary="supported buffer format">
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.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
</event>
<event name="modifier" since="3">
<description summary="supported buffer format modifier">
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.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</event>
</interface>
<interface name="zwp_linux_buffer_params_v1" version="3">
<description summary="parameters for creating a dmabuf-based wl_buffer">
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.
</description>
<enum name="error">
<entry name="already_used" value="0"
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
<entry name="plane_idx" value="1"
summary="plane index out of bounds"/>
<entry name="plane_set" value="2"
summary="the plane index was already set"/>
<entry name="incomplete" value="3"
summary="missing or too many planes to create a buffer"/>
<entry name="invalid_format" value="4"
summary="format not supported"/>
<entry name="invalid_dimensions" value="5"
summary="invalid width or height"/>
<entry name="out_of_bounds" value="6"
summary="offset + stride * height goes out of dmabuf bounds"/>
<entry name="invalid_wl_buffer" value="7"
summary="invalid wl_buffer resulted from importing dmabufs via
the create_immed request on given buffer_params"/>
</enum>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Cleans up the temporary data sent to the server for dmabuf-based
wl_buffer creation.
</description>
</request>
<request name="add">
<description summary="add a dmabuf to the temporary set">
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.
</description>
<arg name="fd" type="fd" summary="dmabuf fd"/>
<arg name="plane_idx" type="uint" summary="plane index"/>
<arg name="offset" type="uint" summary="offset in bytes"/>
<arg name="stride" type="uint" summary="stride in bytes"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</request>
<enum name="flags">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
<entry name="interlaced" value="2" summary="content is interlaced"/>
<entry name="bottom_first" value="4" summary="bottom field first"/>
</enum>
<request name="create">
<description summary="create a wl_buffer from the given dmabufs">
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.
</description>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" summary="see enum flags"/>
</request>
<event name="created">
<description summary="buffer creation succeeded">
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.
</description>
<arg name="buffer" type="new_id" interface="wl_buffer"
summary="the newly created wl_buffer"/>
</event>
<event name="failed">
<description summary="buffer creation failed">
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.
</description>
</event>
<request name="create_immed" since="2">
<description summary="immediately create a wl_buffer from the given
dmabufs">
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.
</description>
<arg name="buffer_id" type="new_id" interface="wl_buffer"
summary="id for the newly created wl_buffer"/>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" summary="see enum flags"/>
</request>
</interface>
</protocol>

View File

@ -19,11 +19,6 @@ client_protocols = [
'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 = []

View File

@ -1,278 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_data_control_unstable_v1">
<copyright>
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.
</copyright>
<description summary="control data devices">
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.
</description>
<interface name="zwlr_data_control_manager_v1" version="2">
<description summary="manager to control data devices">
This interface is a manager that allows creating per-seat data device
controls.
</description>
<request name="create_data_source">
<description summary="create a new data source">
Create a new data source.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_source_v1"
summary="data source to create"/>
</request>
<request name="get_data_device">
<description summary="get a data device for a seat">
Create a data device that can be used to manage a seat's selection.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_device_v1"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_data_control_device_v1" version="2">
<description summary="manage a data device for a seat">
This interface allows a client to manage a seat's selection.
When the seat is destroyed, this object becomes inert.
</description>
<request name="set_selection">
<description summary="copy data to the selection">
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.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this data device">
Destroys the data device object.
</description>
</request>
<event name="data_offer">
<description summary="introduce a new wlr_data_control_offer">
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.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
</event>
<event name="selection">
<description summary="advertise new selection">
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.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
</event>
<event name="finished">
<description summary="this data control is no longer valid">
This data control object is no longer valid and should be destroyed by
the client.
</description>
</event>
<!-- Version 2 additions -->
<event name="primary_selection" since="2">
<description summary="advertise new primary selection">
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.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
</event>
<request name="set_primary_selection" since="2">
<description summary="copy data to the primary selection">
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.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
</request>
<enum name="error" since="2">
<entry name="used_source" value="1"
summary="source given to set_selection or set_primary_selection was already used before"/>
</enum>
</interface>
<interface name="zwlr_data_control_source_v1" version="1">
<description summary="offer to transfer data">
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.
</description>
<enum name="error">
<entry name="invalid_offer" value="1"
summary="offer sent after wlr_data_control_device.set_selection"/>
</enum>
<request name="offer">
<description summary="add an offered MIME type">
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.
</description>
<arg name="mime_type" type="string"
summary="MIME type offered by the data source"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this source">
Destroys the data source object.
</description>
</request>
<event name="send">
<description summary="send the data">
Request for data from the client. Send the data as the specified MIME
type over the passed file descriptor, then close it.
</description>
<arg name="mime_type" type="string" summary="MIME type for the data"/>
<arg name="fd" type="fd" summary="file descriptor for the data"/>
</event>
<event name="cancelled">
<description summary="selection was cancelled">
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.
</description>
</event>
</interface>
<interface name="zwlr_data_control_offer_v1" version="1">
<description summary="offer to transfer data">
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.
</description>
<request name="receive">
<description summary="request that the data is transferred">
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.
</description>
<arg name="mime_type" type="string"
summary="MIME type desired by receiver"/>
<arg name="fd" type="fd" summary="file descriptor for data transfer"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this offer">
Destroys the data offer object.
</description>
</request>
<event name="offer">
<description summary="advertise offered MIME type">
Sent immediately after creating the wlr_data_control_offer object.
One event per offered MIME type.
</description>
<arg name="mime_type" type="string" summary="offered MIME type"/>
</event>
</interface>
</protocol>

View File

@ -1,601 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_output_management_unstable_v1">
<copyright>
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.
</copyright>
<description summary="protocol to configure output devices">
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.
</description>
<interface name="zwlr_output_manager_v1" version="4">
<description summary="output device configuration manager">
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.
</description>
<event name="head">
<description summary="introduce a new head">
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.
</description>
<arg name="head" type="new_id" interface="zwlr_output_head_v1"/>
</event>
<event name="done">
<description summary="sent all information about current configuration">
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.
</description>
<arg name="serial" type="uint" summary="current configuration serial"/>
</event>
<request name="create_configuration">
<description summary="create a new output configuration object">
Create a new output configuration object. This allows to update head
properties.
</description>
<arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/>
<arg name="serial" type="uint"/>
</request>
<request name="stop">
<description summary="stop sending events">
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.
</description>
</request>
<event name="finished" type="destructor">
<description summary="the compositor has finished with the manager">
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.
</description>
</event>
</interface>
<interface name="zwlr_output_head_v1" version="4">
<description summary="output device">
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.
</description>
<event name="name">
<description summary="head name">
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.
</description>
<arg name="name" type="string"/>
</event>
<event name="description">
<description summary="head description">
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.
</description>
<arg name="description" type="string"/>
</event>
<event name="physical_size">
<description summary="head physical size">
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).
</description>
<arg name="width" type="int" summary="width in millimeters of the output"/>
<arg name="height" type="int" summary="height in millimeters of the output"/>
</event>
<event name="mode">
<description summary="introduce a mode">
This event introduces a mode for this head. It is sent once per
supported mode.
</description>
<arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/>
</event>
<event name="enabled">
<description summary="head is enabled or disabled">
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.
</description>
<arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/>
</event>
<event name="current_mode">
<description summary="current mode">
This event describes the mode currently in use for this head. It is only
sent if the output is enabled.
</description>
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
</event>
<event name="position">
<description summary="current position">
This events describes the position of the head in the global compositor
space. It is only sent if the output is enabled.
</description>
<arg name="x" type="int"
summary="x position within the global compositor space"/>
<arg name="y" type="int"
summary="y position within the global compositor space"/>
</event>
<event name="transform">
<description summary="current transformation">
This event describes the transformation currently applied to the head.
It is only sent if the output is enabled.
</description>
<arg name="transform" type="int" enum="wl_output.transform"/>
</event>
<event name="scale">
<description summary="current scale">
This events describes the scale of the head in the global compositor
space. It is only sent if the output is enabled.
</description>
<arg name="scale" type="fixed"/>
</event>
<event name="finished">
<description summary="the head has disappeared">
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.
</description>
</event>
<!-- Version 2 additions -->
<event name="make" since="2">
<description summary="head manufacturer">
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.
</description>
<arg name="make" type="string"/>
</event>
<event name="model" since="2">
<description summary="head model">
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.
</description>
<arg name="model" type="string"/>
</event>
<event name="serial_number" since="2">
<description summary="head serial number">
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.
</description>
<arg name="serial_number" type="string"/>
</event>
<!-- Version 3 additions -->
<request name="release" type="destructor" since="3">
<description summary="destroy the head object">
This request indicates that the client will no longer use this head
object.
</description>
</request>
<!-- Version 4 additions -->
<enum name="adaptive_sync_state" since="4">
<entry name="disabled" value="0" summary="adaptive sync is disabled"/>
<entry name="enabled" value="1" summary="adaptive sync is enabled"/>
</enum>
<event name="adaptive_sync" since="4">
<description summary="current adaptive sync state">
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.
</description>
<arg name="state" type="uint" enum="adaptive_sync_state"/>
</event>
</interface>
<interface name="zwlr_output_mode_v1" version="3">
<description summary="output mode">
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.
</description>
<event name="size">
<description summary="mode size">
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.
</description>
<arg name="width" type="int" summary="width of the mode in hardware units"/>
<arg name="height" type="int" summary="height of the mode in hardware units"/>
</event>
<event name="refresh">
<description summary="mode refresh rate">
This event describes the mode's fixed vertical refresh rate. It is only
sent if the mode has a fixed refresh rate.
</description>
<arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
</event>
<event name="preferred">
<description summary="mode is preferred">
This event advertises this mode as preferred.
</description>
</event>
<event name="finished">
<description summary="the mode has disappeared">
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.
</description>
</event>
<!-- Version 3 additions -->
<request name="release" type="destructor" since="3">
<description summary="destroy the mode object">
This request indicates that the client will no longer use this mode
object.
</description>
</request>
</interface>
<interface name="zwlr_output_configuration_v1" version="4">
<description summary="output configuration">
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.
</description>
<enum name="error">
<entry name="already_configured_head" value="1"
summary="head has been configured twice"/>
<entry name="unconfigured_head" value="2"
summary="head has not been configured"/>
<entry name="already_used" value="3"
summary="request sent after configuration has been applied or tested"/>
</enum>
<request name="enable_head">
<description summary="enable and configure a head">
Enable a head. This request creates a head configuration object that can
be used to change the head's properties.
</description>
<arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1"
summary="a new object to configure the head"/>
<arg name="head" type="object" interface="zwlr_output_head_v1"
summary="the head to be enabled"/>
</request>
<request name="disable_head">
<description summary="disable a head">
Disable a head.
</description>
<arg name="head" type="object" interface="zwlr_output_head_v1"
summary="the head to be disabled"/>
</request>
<request name="apply">
<description summary="apply the configuration">
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.
</description>
</request>
<request name="test">
<description summary="test the configuration">
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.
</description>
</request>
<event name="succeeded">
<description summary="configuration changes succeeded">
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.
</description>
</event>
<event name="failed">
<description summary="configuration changes failed">
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.
</description>
</event>
<event name="cancelled">
<description summary="configuration has been cancelled">
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.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the output configuration">
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.
</description>
</request>
</interface>
<interface name="zwlr_output_configuration_head_v1" version="4">
<description summary="head configuration">
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.
</description>
<enum name="error">
<entry name="already_set" value="1" summary="property has already been set"/>
<entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/>
<entry name="invalid_custom_mode" value="3" summary="mode is invalid"/>
<entry name="invalid_transform" value="4" summary="transform value outside enum"/>
<entry name="invalid_scale" value="5" summary="scale negative or zero"/>
<entry name="invalid_adaptive_sync_state" value="6" since="4"
summary="invalid enum value used in the set_adaptive_sync request"/>
</enum>
<request name="set_mode">
<description summary="set the mode">
This request sets the head's mode.
</description>
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
</request>
<request name="set_custom_mode">
<description summary="set a custom 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.
</description>
<arg name="width" type="int" summary="width of the mode in hardware units"/>
<arg name="height" type="int" summary="height of the mode in hardware units"/>
<arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/>
</request>
<request name="set_position">
<description summary="set the position">
This request sets the head's position in the global compositor space.
</description>
<arg name="x" type="int" summary="x position in the global compositor space"/>
<arg name="y" type="int" summary="y position in the global compositor space"/>
</request>
<request name="set_transform">
<description summary="set the transform">
This request sets the head's transform.
</description>
<arg name="transform" type="int" enum="wl_output.transform"/>
</request>
<request name="set_scale">
<description summary="set the scale">
This request sets the head's scale.
</description>
<arg name="scale" type="fixed"/>
</request>
<!-- Version 4 additions -->
<request name="set_adaptive_sync" since="4">
<description summary="enable/disable adaptive sync">
This request enables/disables adaptive sync. Adaptive sync is also
known as Variable Refresh Rate or VRR.
</description>
<arg name="state" type="uint" enum="zwlr_output_head_v1.adaptive_sync_state"/>
</request>
</interface>
</protocol>

View File

@ -1,128 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_output_power_management_unstable_v1">
<copyright>
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.
</copyright>
<description summary="Control power management modes of outputs">
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.
</description>
<interface name="zwlr_output_power_manager_v1" version="1">
<description summary="manager to create per-output power management">
This interface is a manager that allows creating per-output power
management mode controls.
</description>
<request name="get_output_power">
<description summary="get a power management for an output">
Create a output power management mode control that can be used to
adjust the power management mode for a given output.
</description>
<arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_output_power_v1" version="1">
<description summary="adjust power management mode for an output">
This object offers requests to set the power management mode of
an output.
</description>
<enum name="mode">
<entry name="off" value="0"
summary="Output is turned off."/>
<entry name="on" value="1"
summary="Output is turned on, no power saving"/>
</enum>
<enum name="error">
<entry name="invalid_mode" value="1" summary="inexistent power save mode"/>
</enum>
<request name="set_mode">
<description summary="Set an outputs power save mode">
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.
</description>
<arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
</request>
<event name="mode">
<description summary="Report a power management mode change">
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.
</description>
<arg name="mode" type="uint" enum="mode"
summary="the output's new power management mode"/>
</event>
<event name="failed">
<description summary="object no longer valid">
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.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy this power management">
Destroys the output power management mode control object.
</description>
</request>
</interface>
</protocol>

View File

@ -38,7 +38,7 @@
interface version number is reset.
</description>
<interface name="zwlr_screencopy_manager_v1" version="3">
<interface name="zwlr_screencopy_manager_v1" version="2">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
@ -80,18 +80,13 @@
</request>
</interface>
<interface name="zwlr_screencopy_frame_v1" version="3">
<interface name="zwlr_screencopy_frame_v1" version="2">
<description summary="a frame ready for copy">
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.
When created, a "buffer" event will be sent. 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.
If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event.
@ -101,12 +96,14 @@
</description>
<event name="buffer">
<description summary="wl_shm buffer information">
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.
<description summary="buffer information">
Provides information about the frame's buffer. This event is sent once
as soon as the frame is created.
The client should then create a buffer with the provided attributes, and
send a "copy" request.
</description>
<arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
<arg name="format" type="uint" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/>
@ -115,9 +112,8 @@
<request name="copy">
<description summary="copy the frame">
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.
correct size, see zwlr_screencopy_frame_v1.buffer. 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.
@ -207,26 +203,5 @@
<arg name="width" type="uint" summary="current width"/>
<arg name="height" type="uint" summary="current height"/>
</event>
<!-- Version 3 additions -->
<event name="linux_dmabuf" since="3">
<description summary="linux-dmabuf buffer information">
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.
</description>
<arg name="format" type="uint" summary="fourcc pixel format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
</event>
<event name="buffer_done" since="3">
<description summary="all buffer types reported">
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.
</description>
</event>
</interface>
</protocol>

View File

@ -0,0 +1,48 @@
precision mediump float;
uniform sampler2D u_tex0;
uniform sampler2D u_tex1;
uniform int u_width;
uniform int u_height;
varying vec2 v_texture;
bool is_pixel_damaged(vec2 pos)
{
return texture2D(u_tex0, pos).rgb != texture2D(u_tex1, pos).rgb;
}
bool is_region_damaged(vec2 pos)
{
int x_correction = u_width - (u_width / 32) * 32;
int y_correction = u_height - (u_height / 32) * 32;
float x_scaling = float(u_width) / float(u_width - x_correction);
float y_scaling = float(u_height) / float(u_height - y_correction);
float x_pixel = 1.0 / float(u_width);
float y_pixel = 1.0 / float(u_height);
bool r = false;
for (int y = -17; y < 17; ++y)
for (int x = -17; x < 17; ++x) {
float px = float(x) * x_pixel;
float py = float(y) * y_pixel;
float spx = x_scaling * (pos.x + px);
float spy = y_scaling * (pos.y + py);
if (is_pixel_damaged(vec2(spx, spy)))
r = true;
}
return r;
}
void main()
{
float r = float(is_region_damaged(v_texture));
gl_FragColor = vec4(r);
}

View File

@ -0,0 +1,10 @@
attribute vec2 pos;
attribute vec2 texture;
varying vec2 v_texture;
void main()
{
v_texture = vec2(texture.s, 1.0 - texture.t);
gl_Position = vec4(pos, 0, 1);
}

View File

@ -0,0 +1,12 @@
#extension GL_OES_EGL_image_external: require
precision mediump float;
uniform samplerExternalOES u_tex0;
varying vec2 v_texture;
void main()
{
gl_FragColor = texture2D(u_tex0, v_texture);
}

View File

@ -0,0 +1,12 @@
uniform mat2 u_proj;
attribute vec2 pos;
attribute vec2 texture;
varying vec2 v_texture;
void main()
{
v_texture = vec2(texture.s, 1.0 - texture.t);
gl_Position = vec4(u_proj * pos, 0, 1);
}

View File

@ -0,0 +1,10 @@
precision mediump float;
uniform sampler2D u_tex0;
varying vec2 v_texture;
void main()
{
gl_FragColor = texture2D(u_tex0, v_texture);
}

View File

@ -0,0 +1,12 @@
uniform mat2 u_proj;
attribute vec2 pos;
attribute vec2 texture;
varying vec2 v_texture;
void main()
{
v_texture = texture;
gl_Position = vec4(u_proj * pos, 0, 1);
}

View File

@ -1,463 +0,0 @@
/*
* 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 <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <stdbool.h>
#include <assert.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include <wayland-client.h>
#include <pixman.h>
#include <neatvnc.h>
#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"
#ifdef ENABLE_SCREENCOPY_DMABUF
#include <gbm.h>
#include <sys/ioctl.h>
#include <fcntl.h>
// #ifdef HAVE_LINUX_DMA_HEAP
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#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;
extern struct gbm_device* gbm_device;
enum wv_buffer_type wv_buffer_get_available_types(void)
{
enum wv_buffer_type type = 0;
if (wl_shm)
type |= WV_BUFFER_SHM;
#ifdef ENABLE_SCREENCOPY_DMABUF
if (zwp_linux_dmabuf && gbm_device)
type |= WV_BUFFER_DMABUF;
#endif
return type;
}
struct wv_buffer* wv_buffer_create_shm(int width,
int height, int stride, uint32_t fourcc)
{
assert(wl_shm);
enum wl_shm_format wl_fmt = fourcc_to_wl_shm(fourcc);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_SHM;
self->width = width;
self->height = height;
self->stride = stride;
self->format = fourcc;
self->size = height * stride;
int fd = shm_alloc_fd(self->size);
if (fd < 0)
goto failure;
self->pixels = mmap(NULL, self->size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (!self->pixels)
goto mmap_failure;
struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, fd, self->size);
if (!pool)
goto pool_failure;
self->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
stride, wl_fmt);
wl_shm_pool_destroy(pool);
if (!self->wl_buffer)
goto shm_failure;
int bpp = pixel_size_from_fourcc(fourcc);
assert(bpp > 0);
self->nvnc_fb = nvnc_fb_from_buffer(self->pixels, width, height, fourcc,
stride / bpp);
if (!self->nvnc_fb) {
goto nvnc_fb_failure;
}
nvnc_set_userdata(self->nvnc_fb, self, NULL);
pixman_region_init(&self->damage);
close(fd);
return self;
nvnc_fb_failure:
wl_buffer_destroy(self->wl_buffer);
shm_failure:
pool_failure:
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
#ifdef ENABLE_SCREENCOPY_DMABUF
#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)
{
assert(gbm_device);
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_device, 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
static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
uint32_t fourcc)
{
assert(zwp_linux_dmabuf);
assert(gbm_device);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_DMABUF;
self->width = width;
self->height = height;
self->format = fourcc;
// Checks not needed anymore. Fixed with SCANOUT and within neatvnc for most GPUs.
// But this could still fail!
//#ifdef HAVE_LINUX_DMA_HEAP
self->bo = have_linux_cma() ?
create_cma_gbm_bo(width, height, fourcc) :
gbm_bo_create(gbm_device, width, height, fourcc,
GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
//#endif
// self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
// GBM_BO_USE_RENDERING);
if (!self->bo)
goto bo_failure;
struct zwp_linux_buffer_params_v1* params;
params = zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf);
if (!params)
goto params_failure;
uint32_t offset = gbm_bo_get_offset(self->bo, 0);
uint32_t stride = gbm_bo_get_stride(self->bo);
uint64_t mod = gbm_bo_get_modifier(self->bo);
int fd = gbm_bo_get_fd(self->bo);
if (fd < 0)
goto fd_failure;
zwp_linux_buffer_params_v1_add(params, fd, 0, offset, stride,
mod >> 32, mod & 0xffffffff);
self->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params, width,
height, fourcc, /* flags */ 0);
zwp_linux_buffer_params_v1_destroy(params);
close(fd);
if (!self->wl_buffer)
goto buffer_failure;
self->nvnc_fb = nvnc_fb_from_gbm_bo(self->bo);
if (!self->nvnc_fb) {
goto nvnc_fb_failure;
}
nvnc_set_userdata(self->nvnc_fb, self, NULL);
return self;
nvnc_fb_failure:
wl_buffer_destroy(self->wl_buffer);
buffer_failure:
fd_failure:
zwp_linux_buffer_params_v1_destroy(params);
params_failure:
gbm_bo_destroy(self->bo);
bo_failure:
free(self);
return NULL;
}
#endif
struct wv_buffer* wv_buffer_create(enum wv_buffer_type type, int width,
int height, int stride, uint32_t fourcc)
{
switch (type) {
case WV_BUFFER_SHM:
return wv_buffer_create_shm(width, height, stride, fourcc);
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
return wv_buffer_create_dmabuf(width, height, fourcc);
#endif
case WV_BUFFER_UNSPEC:;
}
abort();
return NULL;
}
static void wv_buffer_destroy_shm(struct wv_buffer* self)
{
nvnc_fb_unref(self->nvnc_fb);
wl_buffer_destroy(self->wl_buffer);
munmap(self->pixels, self->size);
free(self);
}
#ifdef ENABLE_SCREENCOPY_DMABUF
static void wv_buffer_destroy_dmabuf(struct wv_buffer* self)
{
nvnc_fb_unref(self->nvnc_fb);
wl_buffer_destroy(self->wl_buffer);
gbm_bo_destroy(self->bo);
free(self);
}
#endif
void wv_buffer_destroy(struct wv_buffer* self)
{
pixman_region_fini(&self->damage);
switch (self->type) {
case WV_BUFFER_SHM:
wv_buffer_destroy_shm(self);
return;
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF:
wv_buffer_destroy_dmabuf(self);
return;
#endif
case WV_BUFFER_UNSPEC:;
}
abort();
}
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height)
{
pixman_region_union_rect(&self->damage, &self->damage, x, y, width,
height);
}
void wv_buffer_damage_whole(struct wv_buffer* self)
{
wv_buffer_damage_rect(self, 0, 0, self->width, self->height);
}
void wv_buffer_damage_clear(struct wv_buffer* self)
{
pixman_region_clear(&self->damage);
}
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type type,
int width, int height, int stride, uint32_t format)
{
struct wv_buffer_pool* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
TAILQ_INIT(&self->queue);
self->type = type;
self->width = width;
self->height = height;
self->stride = stride;
self->format = format;
return self;
}
static void wv_buffer_pool_clear(struct wv_buffer_pool* pool)
{
while (!TAILQ_EMPTY(&pool->queue)) {
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
TAILQ_REMOVE(&pool->queue, buffer, link);
wv_buffer_destroy(buffer);
}
}
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool)
{
wv_buffer_pool_clear(pool);
free(pool);
}
void wv_buffer_pool_resize(struct wv_buffer_pool* pool,
enum wv_buffer_type type, int width, int height, int stride,
uint32_t format)
{
if (pool->type != type || pool->width != width || pool->height != height
|| pool->stride != stride || pool->format != format) {
wv_buffer_pool_clear(pool);
}
pool->type = type;
pool->width = width;
pool->height = height;
pool->stride = stride;
pool->format = format;
}
static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
if (pool->type != buffer->type)
return false;
switch (pool->type) {
case WV_BUFFER_SHM:
if (pool->stride != buffer->stride) {
return false;
}
#ifdef ENABLE_SCREENCOPY_DMABUF
/* fall-through */
case WV_BUFFER_DMABUF:
#endif
if (pool->width != buffer->width
|| pool->height != buffer->height
|| pool->format != buffer->format)
return false;
return true;
case WV_BUFFER_UNSPEC:
abort();
}
return false;
}
void wv_buffer_pool__on_release(struct nvnc_fb* fb, void* context)
{
struct wv_buffer* buffer = nvnc_get_userdata(fb);
struct wv_buffer_pool* pool = context;
wv_buffer_pool_release(pool, buffer);
}
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool)
{
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
if (buffer) {
assert(wv_buffer_pool_match_buffer(pool, buffer));
TAILQ_REMOVE(&pool->queue, buffer, link);
return buffer;
}
buffer = wv_buffer_create(pool->type, pool->width, pool->height,
pool->stride, pool->format);
if (buffer)
nvnc_fb_set_release_fn(buffer->nvnc_fb,
wv_buffer_pool__on_release, pool);
return buffer;
}
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
wv_buffer_damage_clear(buffer);
if (wv_buffer_pool_match_buffer(pool, buffer)) {
TAILQ_INSERT_TAIL(&pool->queue, buffer, link);
} else {
wv_buffer_destroy(buffer);
}
}

View File

@ -18,8 +18,6 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <libgen.h>
#include <limits.h>
#include "cfg.h"
@ -108,16 +106,11 @@ static int cfg__load_line(struct cfg* self, char* line)
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;
@ -125,8 +118,6 @@ int cfg_load(struct cfg* self, const char* requested_path)
if (!stream)
return -1;
self->directory = cfg__dirname(path);
char* line = NULL;
size_t len = 0;
int lineno = 0;
@ -138,14 +129,11 @@ int cfg_load(struct cfg* self, const char* requested_path)
goto failure;
}
free(line);
fclose(stream);
return 0;
failure:
cfg_destroy(self);
free(line);
free(self->directory);
fclose(stream);
return lineno;
}
@ -163,5 +151,4 @@ void cfg_destroy(struct cfg* self)
#undef DESTROY_string
#undef DESTROY_uint
#undef DESTROY_bool
free(self->directory);
}

View File

@ -1,928 +0,0 @@
/*
* 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 <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <poll.h>
#include <signal.h>
#include <assert.h>
#include <jansson.h>
#include <sys/param.h>
#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 : "<unknown>");
}
}
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("<null>\n");
break;
case JSON_OBJECT:
json_object_foreach(data, key, value) {
if (!json_has_content(value))
continue;
print_indent(level);
printf("%s: ", key);
print_for_human(value, level + 1);
}
break;
case JSON_ARRAY:
json_array_foreach(data, i, value) {
if (!json_has_content(value))
continue;
print_indent(level);
printf("- ");
print_for_human(value, level + 1);
}
break;
case JSON_STRING:
printf("%s\n", json_string_value(data));
break;
case JSON_INTEGER:
printf("%" JSON_INTEGER_FORMAT "\n", json_integer_value(data));
break;
case JSON_REAL:
printf("%f\n", json_real_value(data));
break;
case JSON_TRUE:
printf("true\n");
break;
case JSON_FALSE:
printf("false\n");
break;
}
}
static void print_event(struct jsonipc_request* event, unsigned flags)
{
if (flags & CTL_CLIENT_PRINT_JSON) {
print_compact_json(event->json);
} else {
printf("%s:\n", event->method);
if (event->params)
print_for_human(event->params, 1);
printf("\n");
}
fflush(stdout);
}
static void send_local_event(struct ctl_client* self, const char* name)
{
struct jsonipc_request* event = jsonipc_event_new(name, NULL);
event->json = jsonipc_request_pack(event, NULL);
print_event(event, self->flags);
jsonipc_request_destroy(event);
}
static void send_startup_event(struct ctl_client* self)
{
send_local_event(self, EVT_LOCAL_STARTUP);
}
static void send_shutdown_event(struct ctl_client* self)
{
send_local_event(self, EVT_LOCAL_SHUTDOWN);
}
static ssize_t ctl_client_send_request(struct ctl_client* self,
struct jsonipc_request* request)
{
json_error_t err;
json_t* packed = jsonipc_request_pack(request, &err);
if (!packed) {
ERROR("Could not encode json: %s", err.text);
return -1;
}
char buffer[512];
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
json_decref(packed);
DEBUG(">> %.*s", len, buffer);
return send(self->fd, buffer, len, MSG_NOSIGNAL);
}
static struct jsonipc_response* ctl_client_run_single_command(struct ctl_client* self,
struct jsonipc_request* request)
{
if (ctl_client_send_request(self, request) < 0)
return NULL;
return ctl_client_wait_for_response(self);
}
static int ctl_client_register_for_events(struct ctl_client* self,
struct jsonipc_request* request)
{
struct jsonipc_response* response = ctl_client_run_single_command(self, request);
if (!response)
return -1;
int result = response->code;
jsonipc_response_destroy(response);
if (result == 0)
send_startup_event(self);
return result;
}
static int ctl_client_reconnect_event_loop(struct ctl_client* self,
struct jsonipc_request* request)
{
if (ctl_client_connect(self, -1) != 0)
return -1;
return ctl_client_register_for_events(self, request);
}
static int block_until_reconnect(struct ctl_client* self,
struct jsonipc_request* request)
{
while (ctl_client_reconnect_event_loop(self, request) != 0)
if (usleep(50000) == -1) {
DEBUG("Interrupted waiting for the IPC socket");
return -1;
}
return 0;
}
static int ctl_client_event_loop(struct ctl_client* self,
struct jsonipc_request* request)
{
int result = ctl_client_register_for_events(self, request);
if (result != 0)
return result;
self->wait_for_events = true;
setup_signals(self);
while (self->wait_for_events) {
DEBUG("Waiting for an event");
json_t* root = read_one_object(self, -1);
if (!root) {
if (errno == ECONNRESET) {
send_shutdown_event(self);
if (self->flags & CTL_CLIENT_RECONNECT &&
block_until_reconnect(
self, request) == 0)
continue;
}
break;
}
struct jsonipc_error err = JSONIPC_ERR_INIT;
struct jsonipc_request* event = jsonipc_event_parse_new(root, &err);
json_decref(root);
print_event(event, self->flags);
jsonipc_request_destroy(event);
}
return 0;
}
static int ctl_client_print_single_command(struct ctl_client* self,
enum cmd_type cmd, struct jsonipc_request* request)
{
struct jsonipc_response* response = ctl_client_run_single_command(self,
request);
if (!response) {
if (errno == ECONNRESET && cmd == CMD_WAYVNC_EXIT)
return 0;
return 1;
}
int result = ctl_client_print_response(self, request, response);
jsonipc_response_destroy(response);
return result;
}
void ctl_client_print_command_list(FILE* stream)
{
fprintf(stream, "Commands:\n");
size_t max_namelen = 0;
for (size_t i = 0; i < CMD_LIST_LEN; ++i) {
if (i == CMD_HELP) // hidden
continue;
max_namelen = MAX(max_namelen, strlen(ctl_command_list[i].name));
}
struct table_printer printer;
table_printer_init(&printer, stdout, max_namelen);
for (size_t i = 0; i < CMD_LIST_LEN; ++i) {
if (i == CMD_HELP) // hidden
continue;
table_printer_print_line(&printer, ctl_command_list[i].name,
ctl_command_list[i].description);
}
fprintf(stream, "\nRun 'wayvncctl command-name --help' for command-specific details.\n");
}
static size_t param_render_length(const struct cmd_param_info* param)
{
return strlen(param->name) + strlen(param->schema) + 1;
}
static void print_event_info(const struct cmd_info* info)
{
printf("%s\n", info->name);
option_parser_print_cmd_summary(info->description, stdout);
if (info->params[0].name != NULL) {
printf("Data fields:\n");
size_t max_namelen = 0;
for (int i = 0; info->params[i].name != NULL; ++i)
max_namelen = MAX(max_namelen, param_render_length(&info->params[i]));
struct table_printer printer;
table_printer_init(&printer, stdout, max_namelen);
for (int i = 0; info->params[i].name != NULL; ++i)
table_printer_print_fmtline(&printer,
info->params[i].description,
"%s: %s", info->params[i].name,
info->params[i].schema);
printf("\n");
}
}
static int print_event_details(const char* evt_name)
{
struct cmd_info* info = ctl_event_by_name(evt_name);
if (info) {
print_event_info(info);
return 0;
}
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i) {
if (strcmp(evt_name, internal_events[i].name) == 0) {
print_event_info(&internal_events[i]);
return 0;
}
}
ERROR("No such event \"%s\"\n", evt_name);
return 1;
}
void ctl_client_print_event_list(FILE* stream)
{
printf("Events:\n");
size_t max_namelen = 0;
for (size_t i = 0; i < EVT_LIST_LEN; ++i)
max_namelen = MAX(max_namelen, strlen(ctl_event_list[i].name));
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i)
max_namelen = MAX(max_namelen, strlen(internal_events[i].name));
struct table_printer printer;
table_printer_init(&printer, stdout, max_namelen);
for (size_t i = 0; i < EVT_LIST_LEN; ++i)
table_printer_print_line(&printer, ctl_event_list[i].name,
ctl_event_list[i].description);
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i)
table_printer_print_line(&printer, internal_events[i].name,
internal_events[i].description);
}
static int print_command_usage(struct ctl_client* self,
enum cmd_type cmd,
struct option_parser* cmd_options,
struct option_parser* parent_options)
{
if (self->flags & CTL_CLIENT_PRINT_JSON) {
ERROR("JSON output is not supported for \"help\" output");
return 1;
}
struct cmd_info* info = ctl_command_by_type(cmd);
if (!info) {
ERROR("No such command");
return 1;
}
printf("Usage: wayvncctl [options] %s", info->name);
option_parser_print_usage(cmd_options, stdout);
printf("\n");
option_parser_print_cmd_summary(info->description, stdout);
if (option_parser_print_arguments(cmd_options, stdout))
printf("\n");
option_parser_print_options(cmd_options, stdout);
printf("\n");
option_parser_print_options(parent_options, stdout);
printf("\n");
if (cmd == CMD_EVENT_RECEIVE) {
ctl_client_print_event_list(stdout);
printf("\n");
}
return 0;
}
int ctl_client_init_cmd_parser(struct option_parser* parser, enum cmd_type cmd)
{
struct cmd_info* info = ctl_command_by_type(cmd);
if (!info) {
printf("Invalid command");
return -1;
}
size_t param_count = 0;
while (info->params[param_count].name != NULL)
param_count++;
// Add 2: one for --help and one to null-terminate the list
size_t alloc_count = param_count + 2;
if (cmd == CMD_EVENT_RECEIVE)
alloc_count++;
struct wv_option* options = calloc(alloc_count, sizeof(*options));
size_t i = 0;
for (; i < param_count; ++i) {
struct wv_option* option = &options[i];
struct cmd_param_info* param = &info->params[i];
option->help = param->description;
if (param->positional) {
option->positional = param->name;
option->help = param->description;
} else {
option->long_opt = param->name;
option->schema = param->schema;
}
}
if (cmd == CMD_EVENT_RECEIVE) {
options[i].long_opt = "show";
options[i].schema = "<event-name>";
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;
}

View File

@ -1,193 +0,0 @@
/*
* 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 <stdlib.h>
#include <string.h>
struct cmd_info ctl_command_list[] = {
[CMD_ATTACH] = { "attach",
"Attach to a running wayland compositor",
{
{ "display", "Display name", "<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)",
"<name>" },
{ "event",
"The event to show (optional)",
"<name>" },
{},
}
},
[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",
"<integer>", 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",
"<string>", 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", \
"<integer>" }, \
{ "connection_count", \
"The total number of connected VNC clients " including " this one.", \
"<integer>" }, \
{ "address", \
"The IP address of this client (may be null)", \
"<name|ip>" }, \
{ "username", \
"The username used to authentice this client (may be null).", \
"<string>" }, \
{},
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",
"<string>" },
{},
},
},
[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", "<string>" },
{}
}
},
[EVT_OUTPUT_REMOVED] = {"output-removed",
"Sent when an output is removed by the compositor",
{
{ "name", "Output name", "<string>" },
{}
}
},
};
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));
}

File diff suppressed because it is too large Load Diff

193
src/damage.c 100644
View File

@ -0,0 +1,193 @@
/*
* 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 <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <sys/param.h>
#include <pixman.h>
#include <aml.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
struct damage_work {
void (*on_done)(struct pixman_region16*, void*);
void* userdata;
uint8_t* buffer;
uint32_t width, height;
struct pixman_region16 damage;
};
struct damage_work* damage_work_new(void)
{
struct damage_work* work = calloc(1, sizeof(*work));
if (!work)
return NULL;
pixman_region_init(&work->damage);
return work;
}
void damage_work_free(void* ud)
{
struct damage_work* work = ud;
free(work->buffer);
pixman_region_fini(&work->damage);
free(work);
}
bool damage_check_32_byte_block(const void* block)
{
const uint64_t* a = block;
return a[0] || a[1] || a[2] || a[3];
}
void damage_check_row(uint8_t* dst, const uint8_t* src, uint32_t width)
{
uint32_t aligned_width = (width / 32) * 32;
for (uint32_t x = 0; x < aligned_width; x += 32)
dst[x / 32] |= damage_check_32_byte_block(&src[x]);
for (uint32_t x = aligned_width; x < width; ++x)
dst[x / 32] |= src[x];
}
void damage_check_tile_row(struct pixman_region16* damage,
uint8_t* row_buffer, const uint8_t* buffer,
uint32_t y_start, uint32_t width, uint32_t height)
{
memset(row_buffer, 0, width / 32);
for (uint32_t y = y_start; y < y_start + height; ++y)
damage_check_row(row_buffer, buffer + y * width, width);
for (uint32_t x = 0; x < width / 32; ++x)
if (row_buffer[x])
pixman_region_union_rect(damage, damage,
x * 32, y_start, 32, 32);
}
void damage_check(struct pixman_region16* damage, const uint8_t* buffer,
uint32_t width, uint32_t height, struct pixman_box16* hint)
{
uint32_t h = UDIV_UP(height, 32);
uint32_t w = UDIV_UP(width, 32);
for (uint32_t y = 0; y < h; ++y)
for (uint32_t x = 0; x < w; ++x)
if (buffer[x + y * w])
pixman_region_union_rect(damage, damage,
x * 32, y * 32, 32, 32);
pixman_region_intersect_rect(damage, damage, 0, 0, width, height);
return;
uint32_t tiled_width = UDIV_UP(width, 32);
uint8_t* row_buffer = malloc(tiled_width);
assert(row_buffer);
for (uint32_t y = 0; y < height; y += 32) {
uint32_t current_height = MIN(32, height - y);
damage_check_tile_row(damage, row_buffer, buffer, y, width,
current_height);
}
pixman_region_intersect_rect(damage, damage, 0, 0, width, height);
free(row_buffer);
}
static void do_damage_check(void* aml_obj)
{
struct damage_work* priv = aml_get_userdata(aml_obj);
damage_check(&priv->damage, priv->buffer, priv->width, priv->height, NULL);
}
static void on_damage_check_done(void* aml_obj)
{
struct damage_work* priv = aml_get_userdata(aml_obj);
priv->on_done(&priv->damage, priv->userdata);
}
// TODO: Use the hint
// TODO: Split rows between jobs
// XXX: This takes ownership of the damage buffer
int damage_check_async(uint8_t* buffer, uint32_t width, uint32_t height,
struct pixman_box16* hint,
void (*on_done)(struct pixman_region16*, void*),
void* userdata)
{
struct damage_work* priv = damage_work_new();
if (!priv)
return -1;
priv->buffer = buffer;
priv->width = width;
priv->height = height;
priv->on_done = on_done;
priv->userdata = userdata;
struct aml_work* work;
work = aml_work_new(do_damage_check, on_damage_check_done, priv,
damage_work_free);
if (!work)
goto oom;
int rc = aml_start(aml_get_default(), work);
aml_unref(work);
return rc;
oom:
damage_work_free(priv);
return -1;
}
void damage_dump(FILE* stream, struct pixman_region16* damage,
uint32_t width, uint32_t height, uint32_t tile_size)
{
uint32_t tiled_width = UDIV_UP(width, tile_size);
uint32_t tiled_height = UDIV_UP(height, tile_size);
fprintf(stream, "\033[2J");
for (uint32_t y = 0; y < tiled_height; ++y) {
for (uint32_t x = 0; x < tiled_width; ++x) {
struct pixman_box16 box = {
.x1 = x * tile_size,
.x2 = ((x + 1) * tile_size) - 1,
.y1 = y * tile_size,
.y2 = ((y + 1) * tile_size) - 1,
};
pixman_region_overlap_t overlap =
pixman_region_contains_rectangle(damage, &box);
putc(overlap == PIXMAN_REGION_IN ? 'x' : ' ', stream);
}
putc('\n', stream);
}
}

View File

@ -1,294 +0,0 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <aml.h>
#include <neatvnc.h>
#include "data-control.h"
struct receive_context {
struct data_control* data_control;
struct zwlr_data_control_offer_v1* offer;
int fd;
FILE* mem_fp;
size_t mem_size;
char* mem_data;
};
static void destroy_receive_context(void* raw_ctx)
{
struct receive_context* ctx = raw_ctx;
int fd = ctx->fd;
if (ctx->mem_fp)
fclose(ctx->mem_fp);
free(ctx->mem_data);
zwlr_data_control_offer_v1_destroy(ctx->offer);
close(fd);
free(ctx);
}
static void on_receive(void* handler)
{
struct receive_context* ctx = aml_get_userdata(handler);
int fd = aml_get_fd(handler);
assert(ctx->fd == fd);
char buf[4096];
ssize_t ret = read(fd, &buf, sizeof(buf));
if (ret > 0) {
fwrite(&buf, 1, ret, ctx->mem_fp);
return;
}
fclose(ctx->mem_fp);
ctx->mem_fp = NULL;
if (ctx->mem_size)
nvnc_send_cut_text(ctx->data_control->server, ctx->mem_data,
ctx->mem_size);
aml_stop(aml_get_default(), handler);
}
static void receive_data(void* data,
struct zwlr_data_control_offer_v1* offer)
{
struct data_control* self = data;
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
nvnc_log(NVNC_LOG_ERROR, "pipe() failed: %m");
return;
}
struct receive_context* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
close(pipe_fd[0]);
close(pipe_fd[1]);
return;
}
zwlr_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]);
wl_display_flush(self->wl_display);
close(pipe_fd[1]);
ctx->fd = pipe_fd[0];
ctx->data_control = self;
ctx->offer = offer;
ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size);
if (!ctx->mem_fp) {
close(ctx->fd);
free(ctx);
nvnc_log(NVNC_LOG_ERROR, "open_memstream() failed: %m");
return;
}
struct aml_handler* handler = aml_handler_new(ctx->fd, on_receive,
ctx, destroy_receive_context);
if (!handler) {
close(ctx->fd);
free(ctx);
return;
}
aml_start(aml_get_default(), handler);
aml_unref(handler);
}
static void data_control_offer(void* data,
struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1,
const char* mime_type)
{
struct data_control* self = data;
if (self->offer)
return;
if (strcmp(mime_type, self->mime_type) != 0) {
return;
}
self->offer = zwlr_data_control_offer_v1;
}
struct zwlr_data_control_offer_v1_listener data_control_offer_listener = {
data_control_offer
};
static void data_control_device_offer(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
if (!id)
return;
zwlr_data_control_offer_v1_add_listener(id, &data_control_offer_listener, data);
}
static void data_control_device_selection(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
struct data_control* self = data;
if (id && self->offer == id) {
receive_data(data, id);
self->offer = NULL;
}
}
static void data_control_device_finished(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1)
{
zwlr_data_control_device_v1_destroy(zwlr_data_control_device_v1);
}
static void data_control_device_primary_selection(void* data,
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
struct zwlr_data_control_offer_v1* id)
{
struct data_control* self = data;
if (id && self->offer == id) {
receive_data(data, id);
self->offer = NULL;
return;
}
}
static struct zwlr_data_control_device_v1_listener data_control_device_listener = {
.data_offer = data_control_device_offer,
.selection = data_control_device_selection,
.finished = data_control_device_finished,
.primary_selection = data_control_device_primary_selection
};
static void
data_control_source_send(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1,
const char* mime_type,
int32_t fd)
{
struct data_control* self = data;
char* d = self->cb_data;
size_t len = self->cb_len;
int ret;
assert(d);
ret = write(fd, d, len);
if (ret < (int)len)
nvnc_log(NVNC_LOG_ERROR, "write from clipboard incomplete");
close(fd);
}
static void data_control_source_cancelled(void* data,
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1)
{
struct data_control* self = data;
if (self->selection == zwlr_data_control_source_v1) {
self->selection = NULL;
}
if (self->primary_selection == zwlr_data_control_source_v1) {
self->primary_selection = NULL;
}
zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1);
}
struct zwlr_data_control_source_v1_listener data_control_source_listener = {
.send = data_control_source_send,
.cancelled = data_control_source_cancelled
};
static struct zwlr_data_control_source_v1* set_selection(struct data_control* self, bool primary) {
struct zwlr_data_control_source_v1* selection;
selection = zwlr_data_control_manager_v1_create_data_source(self->manager);
if (selection == NULL) {
nvnc_log(NVNC_LOG_ERROR, "zwlr_data_control_manager_v1_create_data_source() failed");
free(self->cb_data);
self->cb_data = NULL;
return NULL;
}
zwlr_data_control_source_v1_add_listener(selection, &data_control_source_listener, self);
zwlr_data_control_source_v1_offer(selection, self->mime_type);
if (primary)
zwlr_data_control_device_v1_set_primary_selection(self->device, selection);
else
zwlr_data_control_device_v1_set_selection(self->device, selection);
return selection;
}
void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat)
{
self->wl_display = wl_display;
self->server = server;
self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat);
zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self);
self->selection = NULL;
self->primary_selection = NULL;
self->cb_data = NULL;
self->cb_len = 0;
self->mime_type = "text/plain;charset=utf-8";
}
void data_control_destroy(struct data_control* self)
{
if (self->selection) {
zwlr_data_control_source_v1_destroy(self->selection);
self->selection = NULL;
}
if (self->primary_selection) {
zwlr_data_control_source_v1_destroy(self->primary_selection);
self->primary_selection = NULL;
}
zwlr_data_control_device_v1_destroy(self->device);
free(self->cb_data);
}
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len)
{
if (!len) {
nvnc_log(NVNC_LOG_ERROR, "%s called with 0 length", __func__);
return;
}
free(self->cb_data);
self->cb_data = malloc(len);
if (!self->cb_data) {
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
return;
}
memcpy(self->cb_data, text, len);
self->cb_len = len;
// Set copy/paste buffer
self->selection = set_selection(self, false);
// Set highlight/middle_click buffer
self->primary_selection = set_selection(self, true);
}

206
src/dmabuf.c 100644
View File

@ -0,0 +1,206 @@
/*
* 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 <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <wayland-client.h>
#include <aml.h>
#include <wayland-util.h>
#include "frame-capture.h"
#include "logging.h"
#include "dmabuf.h"
#include "wlr-export-dmabuf-unstable-v1.h"
#include "time-util.h"
#include "render.h"
#define RATE_LIMIT 20.0 // Hz
static void dmabuf_close_fds(struct dmabuf_capture* self)
{
for (size_t i = 0; i < self->frame.n_planes; ++i)
close(self->frame.plane[i].fd);
self->frame.n_planes = 0;
}
static void dmabuf_capture_stop(struct frame_capture* fc)
{
struct dmabuf_capture* self = (void*)fc;
if (aml_stop(aml_get_default(), self->timer) >= 0)
dmabuf_close_fds(self);
fc->status = CAPTURE_STOPPED;
if (self->zwlr_frame) {
zwlr_export_dmabuf_frame_v1_destroy(self->zwlr_frame);
self->zwlr_frame = NULL;
}
}
static void dmabuf_frame_start(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t width, uint32_t height,
uint32_t offset_x, uint32_t offset_y,
uint32_t buffer_flags, uint32_t flags,
uint32_t format,
uint32_t mod_high, uint32_t mod_low,
uint32_t num_objects)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
aml_stop(aml_get_default(), self->timer);
dmabuf_close_fds(self);
uint64_t mod = ((uint64_t)mod_high << 32) | (uint64_t)mod_low;
self->frame.width = width;
self->frame.height = height;
self->frame.n_planes = num_objects;
self->frame.format = format;
self->frame.plane[0].modifier = mod;
self->frame.plane[1].modifier = mod;
self->frame.plane[2].modifier = mod;
self->frame.plane[3].modifier = mod;
fc->damage_hint.x = 0;
fc->damage_hint.y = 0;
fc->damage_hint.width = width;
fc->damage_hint.height = height;
}
static void dmabuf_frame_object(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t index, int32_t fd, uint32_t size,
uint32_t offset, uint32_t stride,
uint32_t plane_index)
{
struct dmabuf_capture* self = data;
self->frame.plane[plane_index].fd = fd;
self->frame.plane[plane_index].size = size;
self->frame.plane[plane_index].offset = offset;
self->frame.plane[plane_index].pitch = stride;
}
static void dmabuf_timer_ready(void* timer)
{
struct dmabuf_capture* self = aml_get_userdata(timer);
struct frame_capture* fc = (struct frame_capture*)self;
if (fc->status != CAPTURE_IN_PROGRESS)
return;
self->last_time = gettime_us();
fc->status = CAPTURE_DONE;
fc->on_done(fc);
dmabuf_close_fds(self);
}
static void dmabuf_frame_ready(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t tv_sec_hi, uint32_t tv_sec_lo,
uint32_t tv_nsec)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
dmabuf_capture_stop(fc);
uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6;
double time_left = (1.0 / RATE_LIMIT - dt) * 1.0e3;
if (time_left >= 0.0) {
aml_set_duration(self->timer, time_left);
aml_start(aml_get_default(), self->timer);
frame_capture_start(fc);
return;
}
self->last_time = now;
fc->status = CAPTURE_DONE;
fc->on_done(fc);
dmabuf_close_fds(self);
}
static void dmabuf_frame_cancel(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t reason)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
dmabuf_capture_stop(fc);
fc->status = reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT
? CAPTURE_FATAL : CAPTURE_FAILED;
fc->on_done(fc);
dmabuf_close_fds(self);
}
static int dmabuf_capture_start(struct frame_capture* fc)
{
struct dmabuf_capture* self = (void*)fc;
static const struct zwlr_export_dmabuf_frame_v1_listener
dmabuf_frame_listener = {
.frame = dmabuf_frame_start,
.object = dmabuf_frame_object,
.ready = dmabuf_frame_ready,
.cancel = dmabuf_frame_cancel,
};
self->zwlr_frame =
zwlr_export_dmabuf_manager_v1_capture_output(self->manager,
fc->overlay_cursor,
fc->wl_output);
if (!self->zwlr_frame)
return -1;
fc->status = CAPTURE_IN_PROGRESS;
zwlr_export_dmabuf_frame_v1_add_listener(self->zwlr_frame,
&dmabuf_frame_listener, self);
return 0;
}
static void dmabuf_capture_render(struct frame_capture* fc,
struct renderer* render, struct nvnc_fb* fb)
{
struct dmabuf_capture* self = (void*)fc;
render_dmabuf(render, &self->frame);
}
void dmabuf_capture_init(struct dmabuf_capture* self)
{
self->timer = aml_timer_new(0, dmabuf_timer_ready, self, NULL);
assert(self->timer);
self->fc.backend.start = dmabuf_capture_start;
self->fc.backend.stop = dmabuf_capture_stop;
self->fc.backend.render = dmabuf_capture_render;
}

View File

@ -1,213 +0,0 @@
/*
* 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 <string.h>
#include <stdbool.h>
#include <errno.h>
#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);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
* 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
@ -18,7 +18,6 @@
* interface.
*/
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
@ -27,25 +26,19 @@
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon.h>
#include <wayland-client.h>
#include <neatvnc.h>
#include "virtual-keyboard-unstable-v1.h"
#include "keyboard.h"
#include "shm.h"
#include "logging.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)
{
@ -91,7 +84,7 @@ static int compare_symbols(const void* a, const void* b)
const struct table_entry* y = b;
if (x->symbol == y->symbol)
return x->code < y->code ? -1 : x->code > y->code;
return x->level < y->level ? -1 : x->level > y->level;
return x->symbol < y->symbol ? -1 : x->symbol > y->symbol;
}
@ -122,28 +115,18 @@ static int create_lookup_table(struct keyboard* self)
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));
xkb_keysym_get_name(entry->symbol, sym_name, sizeof(sym_name));
const char* code_name MAYBE_UNUSED =
const char* code_name =
xkb_keymap_key_get_name(self->keymap, entry->code);
bool is_pressed MAYBE_UNUSED =
intset_is_set(&self->key_state, entry->code);
bool is_pressed = intset_is_set(&self->key_state, entry->code);
nvnc_log(NVNC_LOG_DEBUG, "symbol=%s level=%d code=%s %s", sym_name, entry->level,
log_debug("symbol=%s level=%d code=%s %s\n", sym_name, entry->level,
code_name, is_pressed ? "pressed" : "released");
}
@ -153,7 +136,7 @@ void keyboard_dump_lookup_table(const struct keyboard* self)
keyboard__dump_entry(self, &self->lookup_table[i]);
}
int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names)
int keyboard_init(struct keyboard* self, const char* layout)
{
self->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!self->context)
@ -162,13 +145,15 @@ int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names
if (intset_init(&self->key_state, 0) < 0)
goto key_state_failure;
self->keymap = xkb_keymap_new_from_names(self->context, rule_names, 0);
struct xkb_rule_names rule_names = {
.layout = layout,
.model = "pc105",
};
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;
@ -184,34 +169,25 @@ int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names
if (!keymap_string)
goto keymap_string_failure;
size_t keymap_size = strlen(keymap_string) + 1;
size_t keymap_len = strlen(keymap_string);
int keymap_fd = shm_alloc_fd(keymap_size);
int keymap_fd = shm_alloc_fd(0);
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;
}
// TODO: Check that write finished writing everything
write(keymap_fd, keymap_string, keymap_len);
free(keymap_string);
zwp_virtual_keyboard_v1_keymap(self->virtual_keyboard,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keymap_fd, keymap_size);
keymap_fd, keymap_len);
close(keymap_fd);
return 0;
write_failure:
close(keymap_fd);
fd_failure:
free(keymap_string);
keymap_string_failure:
@ -254,23 +230,11 @@ struct table_entry* keyboard_find_symbol(const struct keyboard* self,
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;
xkb_mod_mask_t depressed, latched, locked, group;
comp = xkb_state_update_key(self->state, code,
is_pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
@ -283,31 +247,17 @@ static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code,
if (!(comp & compmask))
return;
keyboard_send_mods(self);
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);
// TODO: Handle errors
zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed,
latched, locked, group);
}
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)
bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
{
switch (symbol) {
case XKB_KEY_Shift_L:
@ -323,131 +273,53 @@ static bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
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)));
if (!entry)
return; // TODO: Notify the user about this
while (!keyboard_symbol_is_mod(symbol)) {
int layout, level;
layout = xkb_state_key_get_layout(self->state, entry->code);
level = xkb_state_key_get_level(self->state, entry->code, layout);
if (entry->level == level)
break;
if (++entry >= &self->lookup_table[self->lookup_table_length])
return; // TODO: Notify the user about this
if (entry->symbol != symbol)
return; // TODO: Notify the user about this
}
bool was_pressed = intset_is_set(&self->key_state, entry->code);
if (was_pressed == is_pressed)
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;
}
if (is_pressed)
intset_set(&self->key_state, entry->code);
else
intset_clear(&self->key_state, entry->code);
#ifndef NDEBUG
keyboard__dump_entry(self, entry);
#endif
if (!update_key_state(self, entry->code, is_pressed))
return;
// TODO: This could cause some synchronisation problems with other
// keyboards in the seat.
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;
// TODO: Handle errors
zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, entry->code - 8,
is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
: WL_KEYBOARD_KEY_STATE_RELEASED);
}

2318
src/main.c

File diff suppressed because it is too large Load Diff

View File

@ -1,435 +0,0 @@
/*
* 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 <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <sys/param.h>
#include <ctype.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static int count_options(const struct wv_option* opts)
{
int n = 0;
while (opts[n].short_opt || opts[n].long_opt || opts[n].positional)
n++;
return n;
}
void option_parser_init(struct option_parser* self,
const struct wv_option* options)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->n_opts = count_options(options);
self->name = "Options";
}
static int get_left_col_width(const struct wv_option* opts, int n)
{
int max_width = 0;
for (int i = 0; i < n; ++i) {
int width = 0;
if (opts[i].short_opt)
width += 2;
if (opts[i].long_opt)
width += 2 + strlen(opts[i].long_opt);
if (opts[i].short_opt && opts[i].long_opt)
width += 1; // for ','
if (opts[i].schema) {
width += strlen(opts[i].schema);
if (opts[i].long_opt)
width += 1; // for '='
}
if (width > max_width)
max_width = width;
}
return max_width;
}
static const char* format_help(const struct wv_option* opt)
{
if (!opt->default_)
return opt->help;
static char help_buf[256];
snprintf(help_buf, sizeof(help_buf), "%s\nDefault: %s", opt->help, opt->default_);
return help_buf;
}
static void format_option(struct table_printer* printer, const struct wv_option* opt)
{
if (!opt->help || opt->positional)
return;
int n_chars = 0;
char buf[64];
if (opt->short_opt)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
"-%c", opt->short_opt);
if (opt->long_opt)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
"%s--%s", opt->short_opt ? "," : "",
opt->long_opt);
if (opt->schema)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
"%s%s", opt->long_opt ? "=" : "", opt->schema);
table_printer_print_line(printer, buf, format_help(opt));
}
void option_parser_print_options(struct option_parser* self, FILE* stream)
{
fprintf(stream, "%s:\n", self->name);
int left_col_width = get_left_col_width(self->options, self->n_opts);
struct table_printer printer;
table_printer_init(&printer, stream, left_col_width);
for (int i = 0; i < self->n_opts; ++i)
format_option(&printer, &self->options[i]);
}
static void print_string_tolower(FILE* stream, const char *src)
{
for (const char* c = src; *c != '\0'; c++)
fprintf(stream, "%c", tolower(*c));
}
void option_parser_print_usage(struct option_parser* self, FILE* stream)
{
fprintf(stream, " [");
print_string_tolower(stream, self->name);
fprintf(stream, "]");
int optional_paren_count = 0;
for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional)
continue;
const char* open = "<";
const char* close = ">";
if (opt->default_) {
open = "[";
close = ""; // Closed via optional_paren_count loop below
optional_paren_count++;
} else {
// Enforce there must be NO non-optional args after
// we've processed at least one optional arg
assert(optional_paren_count == 0);
}
fprintf(stream, " %s%s%s", open, opt->positional, close);
}
for (int i = 0; i < optional_paren_count; ++i)
fprintf(stream, "]");
}
int option_parser_print_arguments(struct option_parser* self, FILE* stream)
{
size_t max_arg = 0;
for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional || !opt->help || opt->is_subcommand)
continue;
max_arg = MAX(max_arg, strlen(opt->positional));
}
if (!max_arg)
return 0;
fprintf(stream, "Arguments:\n");
struct table_printer printer;
table_printer_init(&printer, stream, max_arg);
int i;
for (i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional || !opt->help || opt->is_subcommand)
continue;
table_printer_print_line(&printer, opt->positional, format_help(opt));
}
return i;
}
static const struct wv_option* find_long_option(
const struct option_parser* self, const char* name)
{
for (int i = 0; i < self->n_opts; ++i) {
if (!self->options[i].long_opt)
continue;
if (strcmp(self->options[i].long_opt, name) == 0)
return &self->options[i];
}
return NULL;
}
static const struct wv_option* find_short_option(
const struct option_parser* self, char name)
{
for (int i = 0; i < self->n_opts; ++i)
if (self->options[i].short_opt == name)
return &self->options[i];
return NULL;
}
static const struct wv_option* find_positional_option(
struct option_parser* self, int position)
{
int current_pos = 0;
for (int i = 0; i < self->n_opts; ++i) {
if (!self->options[i].positional)
continue;
if (current_pos == position)
return &self->options[i];
current_pos += 1;
}
return NULL;
}
static const struct wv_option* find_positional_option_by_name(
const struct option_parser* self, const char*name)
{
for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i];
if (!opt->positional)
continue;
if (strcmp(opt->positional, name) == 0)
return opt;
}
return NULL;
}
static int append_value(struct option_parser* self,
const struct wv_option* option, const char* value)
{
if ((size_t)self->n_values >= ARRAY_SIZE(self->values)) {
fprintf(stderr, "ERROR: Too many arguments!\n");
return -1;
}
struct wv_option_value* dst = &self->values[self->n_values++];
dst->option = option;
strlcpy(dst->value, value, sizeof(dst->value));
return 0;
}
static int parse_long_arg(struct option_parser* self, int argc,
const char* const* argv, int index)
{
int count = 1;
char name[256];
strlcpy(name, argv[index] + 2, sizeof(name));
char* eq = strchr(name, '=');
if (eq)
*eq = '\0';
const struct wv_option* opt = find_long_option(self, name);
if (!opt) {
fprintf(stderr, "ERROR: Unknown option: \"%s\"\n", name);
return -1;
}
const char* value = "1";
if (opt->schema) {
if (eq) {
value = eq + 1;
} else {
if (index + 1 >= argc) {
fprintf(stderr, "ERROR: An argument is required for the \"%s\" option\n",
opt->long_opt);
return -1;
}
value = argv[index + 1];
count += 1;
}
}
if (append_value(self, opt, value) < 0)
return -1;
return count;
}
static int parse_short_args(struct option_parser* self, char argc,
const char* const* argv, int index)
{
int count = 1;
int len = strlen(argv[index]);
for (int i = 1; i < len; ++i) {
char name = argv[index][i];
const struct wv_option* opt = find_short_option(self, name);
if (!opt) {
fprintf(stderr, "ERROR: Unknown option: \"%c\"\n", name);
return -1;
}
const char* value = "1";
if (opt->schema) {
const char* tail = argv[index] + i + 1;
if (tail[0] == '=') {
value = tail + 1;
} else if (tail[0]) {
value = tail;
} else {
if (index + 1 >= argc) {
fprintf(stderr, "ERROR: An argument is required for the \"%c\" option\n",
opt->short_opt);
return -1;
}
value = argv[index + 1];
count += 1;
}
}
if (append_value(self, opt, value) < 0)
return -1;
if (opt->schema)
break;
}
return count;
}
static int parse_positional_arg(struct option_parser* self, char argc,
const char* const* argv, int i)
{
const struct wv_option* opt = find_positional_option(self, self->position);
if (!opt)
return 1;
if (append_value(self, opt, argv[i]) < 0)
return -1;
self->position += 1;
return opt->is_subcommand ? 0 : 1;
}
int option_parser_parse(struct option_parser* self, int argc,
const char* const* argv)
{
int i = 1;
while (i < argc) {
if (argv[i][0] == '-') {
if (argv[i][1] == '-') {
if (argv[i][2] == '\0') {
i++;
break;
}
int rc = parse_long_arg(self, argc, argv, i);
if (rc < 0)
return -1;
i += rc;
} else {
int rc = parse_short_args(self, argc, argv, i);
if (rc < 0)
return -1;
i += rc;
}
} else {
int rc = parse_positional_arg(self, argc, argv, i);
if (rc < 0)
return -1;
if (rc == 0)
break;
i += rc;
}
}
self->remaining_argc = argc - i;
if (self->remaining_argc)
self->remaining_argv = argv + i;
return 0;
}
const char* option_parser_get_value_no_default(const struct option_parser* self,
const char* name)
{
const struct wv_option* opt;
bool is_short = name[0] && !name[1];
for (int i = 0; i < self->n_values; ++i) {
const struct wv_option_value* value = &self->values[i];
opt = value->option;
if (is_short) {
if (opt->short_opt && opt->short_opt == *name)
return value->value;
} else {
if (opt->long_opt && strcmp(opt->long_opt, name) == 0)
return value->value;
}
if (opt->positional && strcmp(opt->positional, name) == 0)
return value->value;
}
return NULL;
}
const char* option_parser_get_value(const struct option_parser* self,
const char* name)
{
const char* value = option_parser_get_value_no_default(self, name);
if (value)
return value;
bool is_short = name[0] && !name[1];
const struct wv_option* opt;
if (is_short) {
opt = find_short_option(self, name[0]);
if (opt)
return opt->default_;
} else {
opt = find_long_option(self, name);
if (opt)
return opt->default_;
opt = find_positional_option_by_name(self, name);
if (opt)
return opt->default_;
}
return NULL;
}
void option_parser_print_cmd_summary(const char* summary, FILE* stream)
{
struct table_printer printer;
table_printer_init(&printer, stream, 0);
fprintf(stream, "\n");
table_printer_indent_and_reflow_text(stream, summary, printer.max_width, 0, 0);
fprintf(stream, "\n");
}

View File

@ -1,283 +0,0 @@
/*
* 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 <neatvnc.h>
#include <string.h>
#include <wayland-client.h>
#include "output.h"
#include "output-management.h"
#include "wlr-output-management-unstable-v1.h"
struct output_manager_head {
struct zwlr_output_head_v1* head;
struct wl_list link;
char* name;
bool enabled;
};
static struct wl_list heads;
static uint32_t last_config_serial;
static struct zwlr_output_manager_v1* wlr_output_manager;
/* single head properties */
static void output_head_name(void* data,
struct zwlr_output_head_v1* output_head, const char* name)
{
struct output_manager_head* head = data;
nvnc_trace("Got head name: %s", name);
free(head->name);
head->name = strdup(name);
}
static void output_head_description(void* data,
struct zwlr_output_head_v1* output_head,
const char* description)
{
nvnc_trace("Got head description: %s", description);
}
static void output_head_physical_size(void* data,
struct zwlr_output_head_v1* output_head,
int32_t width, int32_t height)
{
nvnc_trace("Got head size: %dx%d", width, height);
}
static void output_head_mode(void* data,
struct zwlr_output_head_v1* output_head,
struct zwlr_output_mode_v1* mode)
{
nvnc_trace("Got head mode");
}
static void output_head_enabled(void* data,
struct zwlr_output_head_v1* output_head, int32_t enabled)
{
nvnc_trace("Got head enabled: %s", enabled ? "yes" : "no");
struct output_manager_head* head = data;
head->enabled = !!enabled;
}
static void output_head_current_mode(void* data,
struct zwlr_output_head_v1* output_head,
struct zwlr_output_mode_v1* mode)
{
nvnc_trace("Got head current mode");
}
static void output_head_position(void* data,
struct zwlr_output_head_v1* output_head, int32_t x, int32_t y)
{
nvnc_trace("Got head position: %d,%d", x, y);
}
static void output_head_transform(void* data,
struct zwlr_output_head_v1* output_head, int32_t transform)
{
nvnc_trace("Got head transform: %d", transform);
}
static void output_head_scale(void* data,
struct zwlr_output_head_v1* output_head, wl_fixed_t scale_f)
{
double scale = wl_fixed_to_double(scale_f);
nvnc_trace("Got head scale: %.2f", scale);
}
static void output_head_finished(void* data,
struct zwlr_output_head_v1* output_head)
{
nvnc_trace("head gone, removing");
struct output_manager_head* head = data;
zwlr_output_head_v1_destroy(output_head);
wl_list_remove(&head->link);
free(head->name);
head->name = NULL;
head->head = NULL;
free(head);
}
struct zwlr_output_head_v1_listener wlr_output_head_listener = {
.name = output_head_name,
.description = output_head_description,
.physical_size = output_head_physical_size,
.mode = output_head_mode,
.enabled = output_head_enabled,
.current_mode = output_head_current_mode,
.position = output_head_position,
.transform = output_head_transform,
.scale = output_head_scale,
.finished = output_head_finished,
};
/* config object */
static void output_manager_config_succeeded(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request succeeded");
zwlr_output_configuration_v1_destroy(config);
}
static void output_manager_config_failed(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request failed");
zwlr_output_configuration_v1_destroy(config);
}
static void output_manager_config_cancelled(void* data,
struct zwlr_output_configuration_v1* config)
{
nvnc_trace("config request cancelled");
zwlr_output_configuration_v1_destroy(config);
}
struct zwlr_output_configuration_v1_listener wlr_output_config_listener = {
.succeeded = output_manager_config_succeeded,
.failed = output_manager_config_failed,
.cancelled = output_manager_config_cancelled,
};
/* manager itself */
static void output_manager_done(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
uint32_t serial)
{
last_config_serial = serial;
nvnc_trace("Got new serial: %u", serial);
}
static void output_manager_finished(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1)
{
nvnc_trace("output-manager destroyed");
wlr_output_manager = NULL;
}
static void output_manager_head(void* data,
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
struct zwlr_output_head_v1* output_head)
{
struct output_manager_head* head = calloc(1, sizeof(*head));
if (!head) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
return;
}
head->head = output_head;
wl_list_insert(heads.prev, &head->link);
nvnc_trace("New head, now at %lu", wl_list_length(&heads));
zwlr_output_head_v1_add_listener(head->head,
&wlr_output_head_listener, head);
}
static const struct zwlr_output_manager_v1_listener
wlr_output_manager_listener = {
.head = output_manager_head,
.done = output_manager_done,
.finished = output_manager_finished,
};
/* Public API */
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager)
{
if (wlr_output_manager)
return;
wl_list_init(&heads);
wlr_output_manager = output_manager;
zwlr_output_manager_v1_add_listener(wlr_output_manager,
&wlr_output_manager_listener, NULL);
}
void wlr_output_manager_destroy(void)
{
if (!wlr_output_manager)
return;
struct output_manager_head* head;
struct output_manager_head* tmp;
wl_list_for_each_safe(head, tmp, &heads, link) {
wl_list_remove(&head->link);
free(head->name);
free(head);
}
zwlr_output_manager_v1_destroy(wlr_output_manager);
wlr_output_manager = NULL;
last_config_serial = 0;
}
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height)
{
if (!wlr_output_manager) {
nvnc_log(NVNC_LOG_INFO,
"output-management protocol not available, not resizing output");
return false;
}
if (!output->is_headless) {
nvnc_log(NVNC_LOG_INFO,
"not resizing output %s: not a headless one",
output->name);
return false;
}
// TODO: This could be synced to --max-fps
int refresh_rate = 0;
struct zwlr_output_configuration_v1* config;
struct zwlr_output_configuration_head_v1* config_head;
config = zwlr_output_manager_v1_create_configuration(
wlr_output_manager, last_config_serial);
zwlr_output_configuration_v1_add_listener(config,
&wlr_output_config_listener, NULL);
struct output_manager_head* head;
wl_list_for_each(head, &heads, link) {
if (!head->enabled) {
nvnc_trace("disabling output %s", head->name);
zwlr_output_configuration_v1_disable_head(
config, head->head);
continue;
}
config_head = zwlr_output_configuration_v1_enable_head(
config, head->head);
if (head->name && strcmp(head->name, output->name) == 0) {
nvnc_trace("reconfiguring output %s", head->name);
zwlr_output_configuration_head_v1_set_custom_mode(
config_head, width, height, refresh_rate);
/* It doesn't make any sense to have rotation on a
* headless display, so we set the transform here to be
* sure.
*/
zwlr_output_configuration_head_v1_set_transform(
config_head, WL_OUTPUT_TRANSFORM_NORMAL);
}
}
nvnc_trace("applying new output config");
zwlr_output_configuration_v1_apply(config);
return true;
}

View File

@ -19,20 +19,14 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h>
#include <neatvnc.h>
#include "output.h"
#include "strlcpy.h"
#include "logging.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))
@ -48,14 +42,14 @@ void output_transform_coord(const struct output* self,
break;
case WL_OUTPUT_TRANSFORM_90:
*dst_x = src_y;
*dst_y = self->height - src_x;
*dst_y = self->width - 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_x = self->height - src_y;
*dst_y = src_x;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED:
@ -63,16 +57,16 @@ void output_transform_coord(const struct output* self,
*dst_y = src_y;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
*dst_x = src_y;
*dst_y = src_x;
*dst_x = self->height - src_y;
*dst_y = self->width - 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;
*dst_x = src_y;
*dst_y = src_x;
break;
}
}
@ -129,9 +123,6 @@ static void output_handle_geometry(void* data, struct wl_output* wl_output,
{
struct output* output = data;
if (transform != (int32_t)output->transform)
output->is_transform_changed = true;
output->x = x;
output->y = y;
output->transform = transform;
@ -149,25 +140,12 @@ static void output_handle_mode(void* data, struct wl_output* wl_output,
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,
@ -184,10 +162,7 @@ static const struct wl_output_listener output_listener = {
void output_destroy(struct output* output)
{
if (output->xdg_output)
zxdg_output_v1_destroy(output->xdg_output);
if (output->wlr_output_power)
zwlr_output_power_v1_destroy(output->wlr_output_power);
zxdg_output_v1_destroy(output->xdg_output);
wl_output_destroy(output->wl_output);
free(output);
}
@ -203,6 +178,23 @@ void output_list_destroy(struct wl_list* list)
}
}
struct output* output_new(struct wl_output* wl_output, uint32_t id)
{
struct output* output = calloc(1, sizeof(*output));
if (!output) {
log_error("OOM\n");
return NULL;
}
output->wl_output = wl_output;
output->id = id;
wl_output_add_listener(output->wl_output, &output_listener,
output);
return output;
}
void output_logical_position(void* data, struct zxdg_output_v1* xdg_output,
int32_t x, int32_t y)
{
@ -219,12 +211,6 @@ void output_name(void* data, struct zxdg_output_v1* xdg_output,
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,
@ -233,7 +219,6 @@ void output_description(void* data, struct zxdg_output_v1* xdg_output,
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 = {
@ -244,97 +229,13 @@ static const struct zxdg_output_v1_listener xdg_output_listener = {
.description = output_description,
};
static void output_setup_xdg_output_manager(struct output* self)
void output_set_xdg_output(struct output* self,
struct zxdg_output_v1* xdg_output)
{
if (!xdg_output_manager || self->xdg_output)
return;
struct zxdg_output_v1* xdg_output =
zxdg_output_manager_v1_get_xdg_output(
xdg_output_manager, self->wl_output);
self->xdg_output = xdg_output;
zxdg_output_v1_add_listener(self->xdg_output, &xdg_output_listener,
self);
}
const char* output_power_state_name(enum output_power_state state)
{
switch(state) {
case OUTPUT_POWER_ON:
return "ON";
case OUTPUT_POWER_OFF:
return "OFF";
case OUTPUT_POWER_UNKNOWN:
return "UNKNOWN";
}
abort();
return NULL;
}
static void output_power_mode(void *data,
struct zwlr_output_power_v1 *zwlr_output_power_v1,
uint32_t mode)
{
struct output* self = data;
nvnc_trace("Output %s power state changed to %s", self->name,
(mode == ZWLR_OUTPUT_POWER_V1_MODE_ON) ? "ON" : "OFF");
enum output_power_state old = self->power;
switch (mode) {
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
self->power = OUTPUT_POWER_OFF;
break;
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
self->power = OUTPUT_POWER_ON;
break;
}
if (old != self->power && self->on_power_change)
self->on_power_change(self);
}
static void output_power_failed(void *data,
struct zwlr_output_power_v1 *zwlr_output_power_v1)
{
struct output* self = data;
nvnc_log(NVNC_LOG_WARNING, "Output %s power state failure", self->name);
self->power = OUTPUT_POWER_UNKNOWN;
zwlr_output_power_v1_destroy(self->wlr_output_power);
self->wlr_output_power = NULL;
}
static const struct zwlr_output_power_v1_listener wlr_output_power_listener = {
.mode = output_power_mode,
.failed = output_power_failed,
};
static void output_setup_wlr_output_power_manager(struct output* self)
{
if (!wlr_output_power_manager || self->wlr_output_power)
return;
struct zwlr_output_power_v1* wlr_output_power =
zwlr_output_power_manager_v1_get_output_power(
wlr_output_power_manager,
self->wl_output);
self->wlr_output_power = wlr_output_power;
zwlr_output_power_v1_add_listener(self->wlr_output_power,
&wlr_output_power_listener, self);
}
int output_set_power_state(struct output* output, enum output_power_state state)
{
assert(state != OUTPUT_POWER_UNKNOWN);
if (!output->wlr_output_power) {
errno = ENOENT;
return -1;
}
nvnc_trace("Output %s requesting power %s", output->name,
output_power_state_name(state));
int mode = (state == OUTPUT_POWER_ON) ? ZWLR_OUTPUT_POWER_V1_MODE_ON :
ZWLR_OUTPUT_POWER_V1_MODE_OFF;
zwlr_output_power_v1_set_mode(output->wlr_output_power, mode);
return 0;
self);
}
struct output* output_find_by_id(struct wl_list* list, uint32_t id)
@ -368,50 +269,3 @@ struct output* output_first(struct wl_list* list)
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 ? &current->link : list;
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
iter->next : iter->prev;
if (iter == list) {
if (wl_list_empty(list))
return NULL;
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
iter->next : iter->prev;
}
struct output* output;
return wl_container_of(iter, output, link);
}
void output_setup_wl_managers(struct wl_list* list)
{
struct output* output;
wl_list_for_each(output, list, link) {
output_setup_xdg_output_manager(output);
output_setup_wlr_output_power_manager(output);
}
}
struct output* output_new(struct wl_output* wl_output, uint32_t id)
{
struct output* output = calloc(1, sizeof(*output));
if (!output) {
nvnc_log(NVNC_LOG_ERROR, "OOM");
return NULL;
}
output->wl_output = wl_output;
output->id = id;
output->power = OUTPUT_POWER_UNKNOWN;
wl_output_add_listener(output->wl_output, &output_listener,
output);
output_setup_xdg_output_manager(output);
output_setup_wlr_output_power_manager(output);
return output;
}

View File

@ -1,83 +0,0 @@
/*
* 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 <stdlib.h>
#include <string.h>
#include <security/pam_appl.h>
#include <neatvnc.h>
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;
}

View File

@ -1,83 +0,0 @@
/*
* 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 <pixman.h>
#include <wayland-client.h>
#include <assert.h>
#include <libdrm/drm_fourcc.h>
#include <stdbool.h>
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;
}

View File

@ -50,28 +50,21 @@ static void pointer_set_button_mask(struct pointer* self, uint32_t t,
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;
int axis = WL_POINTER_AXIS_VERTICAL_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);
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, axis,
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);
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, axis,
wl_fixed_from_int(15),
1);
self->current_mask = mask;
}

782
src/render.c 100644
View File

@ -0,0 +1,782 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "logging.h"
#include "render.h"
#include "dmabuf.h"
#include "output.h"
#include "config.h"
#define MAYBE_UNUSED __attribute__((unused))
#define SHADER_PATH PREFIX "/share/wayvnc/shaders"
enum {
ATTR_INDEX_POS = 0,
ATTR_INDEX_TEXTURE,
};
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define XSTR(s) STR(s)
#define STR(s) #s
#define X_GL_EARLY_EXTENSIONS \
X(PFNEGLGETPLATFORMDISPLAYEXTPROC, eglGetPlatformDisplayEXT) \
X(PFNEGLDEBUGMESSAGECONTROLKHRPROC, eglDebugMessageControlKHR) \
X(PFNGLDEBUGMESSAGECALLBACKKHRPROC, glDebugMessageCallbackKHR) \
#define X_GL_LATE_EXTENSIONS \
X(PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \
X(PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \
X(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, glEGLImageTargetTexture2DOES) \
#define X_GL_EXTENSIONS \
X_GL_EARLY_EXTENSIONS \
X_GL_LATE_EXTENSIONS \
#define X(type, name) type name;
X_GL_EXTENSIONS
#undef X
static const float transforms[][4] = {
[WL_OUTPUT_TRANSFORM_NORMAL] = {
1.0f, 0.0f,
0.0f, 1.0f,
},
[WL_OUTPUT_TRANSFORM_90] = {
0.0f, -1.0f,
1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_180] = {
-1.0f, 0.0f,
0.0f, -1.0f,
},
[WL_OUTPUT_TRANSFORM_270] = {
0.0f, 1.0f,
-1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED] = {
-1.0f, 0.0f,
0.0f, 1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_90] = {
0.0f, -1.0f,
-1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_180] = {
1.0f, 0.0f,
0.0f, -1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_270] = {
0.0f, 1.0f,
1.0f, 0.0f,
},
};
int gl_format_from_fourcc(GLenum* result, uint32_t format)
{
switch (format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
*result = GL_BGRA_EXT;
return 0;
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
*result = GL_RGBA;
return 0;
}
return -1;
}
static inline void* gl_load_single_extension(const char* name)
{
void* ext = eglGetProcAddress(name);
if (!ext)
log_debug("GL: Failed to load procedure: %s\n", name);
return ext;
}
static int gl_load_early_extensions(void)
{
#define X(type, name) \
name = gl_load_single_extension(XSTR(name)); \
if (!name) \
return -1;
X_GL_EARLY_EXTENSIONS
#undef X
return 0;
}
static int gl_load_late_extensions(void)
{
#define X(type, name) \
name = gl_load_single_extension(XSTR(name)); \
if (!name) \
return -1;
X_GL_LATE_EXTENSIONS
#undef X
return 0;
}
MAYBE_UNUSED
static void egl_log(EGLenum error, const char* command, EGLint msg_type,
EGLLabelKHR thread, EGLLabelKHR obj, const char *msg)
{
(void)error;
(void)msg_type;
(void)thread;
(void)obj;
log_debug("EGL: %s: %s\n", command, msg);
}
MAYBE_UNUSED
static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity,
GLsizei len, const GLchar *msg, const void *user)
{
(void)src;
(void)type;
(void)id;
(void)severity;
(void)len;
(void)user;
log_debug("GLES2: %s\n", msg);
}
static void gl_debug_init()
{
#ifndef NDEBUG
static const EGLAttrib debug_attribs[] = {
EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
EGL_NONE,
};
eglDebugMessageControlKHR(egl_log, debug_attribs);
glEnable(GL_DEBUG_OUTPUT_KHR);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
glDebugMessageCallbackKHR(gles2_log, NULL);
#endif
}
static void gl_shader_log(GLuint shader)
{
GLint len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
char* log = malloc(len);
assert(log);
glGetShaderInfoLog(shader, len, &len, log);
fwrite(log, 1, len, stderr);
free(log);
}
static int gl_load_shader(GLuint* dst, const char* path, const char* source,
GLenum type)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint is_compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled);
if (!is_compiled) {
log_error("Failed to compile shader: %s\n", path);
gl_shader_log(shader);
glDeleteShader(shader);
return -1;
}
*dst = shader;
return 0;
}
static char* read_file(const char* path)
{
FILE* stream = fopen(path, "r");
if (!stream)
return NULL;
size_t size = 4096;
size_t rsize = 0;
char* contents = malloc(size);
if (!contents)
goto alloc_failure;
while (1) {
rsize += fread(contents + rsize, 1, size - rsize, stream);
if (rsize < size)
break;
size *= 2;
contents = realloc(contents, size);
if (!contents)
goto read_failure;
}
if (ferror(stream))
goto read_failure;
if (rsize == size) {
contents = realloc(contents, size + 1);
if (!contents)
goto read_failure;
}
contents[rsize] = '\0';
fclose(stream);
return contents;
read_failure:
free(contents);
alloc_failure:
fclose(stream);
return NULL;
}
static char* read_file_at_path(const char* prefix, const char* file)
{
char path[256];
snprintf(path, sizeof(path), "%s/%s", prefix, file);
path[sizeof(path) - 1] = '\0';
return read_file(path);
}
static int gl_load_shader_from_file(GLuint* dst, const char* file, GLenum type)
{
char* source = read_file_at_path("shaders", file);
if (!source)
source = read_file_at_path(SHADER_PATH, file);
if (!source)
return -1;
int rc = gl_load_shader(dst, file, source, type);
free(source);
return rc;
}
static int gl_compile_shader_program(GLuint* dst, const char* vertex_path,
const char* fragment_path)
{
int rc = -1;
GLuint vertex, fragment;
if (gl_load_shader_from_file(&vertex, vertex_path, GL_VERTEX_SHADER) < 0)
return -1;
if (gl_load_shader_from_file(&fragment, fragment_path,
GL_FRAGMENT_SHADER) < 0)
goto fragment_failure;
GLuint program = glCreateProgram();
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glBindAttribLocation(program, ATTR_INDEX_POS, "pos");
glBindAttribLocation(program, ATTR_INDEX_TEXTURE, "texture");
glLinkProgram(program);
glDeleteShader(vertex);
glDeleteShader(fragment);
GLint is_linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &is_linked);
if (!is_linked) {
log_error("Failed to link shaders %s and %s\n", vertex_path,
fragment_path);
gl_shader_log(program);
glDeleteProgram(program);
goto program_failure;
}
*dst = program;
rc = 0;
program_failure:
glDeleteShader(fragment);
fragment_failure:
glDeleteShader(vertex);
return rc;
}
void gl_clear(void)
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
}
void gl_draw(void)
{
static const GLfloat s_vertices[4][2] = {
{ -1.0, 1.0 },
{ 1.0, 1.0 },
{ -1.0, -1.0 },
{ 1.0, -1.0 },
};
static const GLfloat s_positions[4][2] = {
{ 0, 0 },
{ 1, 0 },
{ 0, 1 },
{ 1, 1 },
};
gl_clear();
glVertexAttribPointer(ATTR_INDEX_POS, 2, GL_FLOAT, GL_FALSE, 0,
s_vertices);
glVertexAttribPointer(ATTR_INDEX_TEXTURE, 2, GL_FLOAT, GL_FALSE, 0,
s_positions);
glEnableVertexAttribArray(ATTR_INDEX_POS);
glEnableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDisableVertexAttribArray(ATTR_INDEX_POS);
}
void renderer_swap(struct renderer* self)
{
self->frame_index ^= 1;
}
void render_frame(struct renderer* self)
{
uint32_t width = output_get_transformed_width(self->output);
uint32_t height = output_get_transformed_height(self->output);
renderer_swap(self);
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[self->frame_index].fbo);
glViewport(0, 0, width, height);
glUseProgram(self->frame_shader.program);
glUniform1i(self->frame_shader.u_tex0, 0);
const float* proj = transforms[self->output->transform];
glUniformMatrix2fv(self->frame_shader.u_proj, 1, GL_FALSE, proj);
gl_draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void render_damage(struct renderer* self)
{
uint32_t width = output_get_transformed_width(self->output);
uint32_t height = output_get_transformed_height(self->output);
glBindFramebuffer(GL_FRAMEBUFFER, self->damage_fbo.fbo);
glViewport(0, 0, UDIV_UP(width, 32), UDIV_UP(height, 32));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self->frame_fbo[self->frame_index].tex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, self->frame_fbo[!self->frame_index].tex);
glUseProgram(self->damage_shader.program);
glUniform1i(self->damage_shader.u_tex0, 0);
glUniform1i(self->damage_shader.u_tex1, 1);
glUniform1i(self->damage_shader.u_width, width);
glUniform1i(self->damage_shader.u_height, height);
gl_draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void destroy_fbo(struct renderer_fbo* fbo)
{
glDeleteFramebuffers(1, &fbo->fbo);
glDeleteRenderbuffers(1, &fbo->rbo);
if (fbo->tex)
glDeleteTextures(1, &fbo->tex);
}
int create_fbo(struct renderer_fbo* dst, GLint format, uint32_t width,
uint32_t height)
{
GLuint rbo = 0;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rbo);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
log_error("Framebuffer incomplete\n");
return -1;
}
dst->fbo = fbo;
dst->rbo = rbo;
dst->tex = 0;
return 0;
}
int create_textured_fbo(struct renderer_fbo* dst, GLint format, uint32_t width,
uint32_t height)
{
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format,
GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, tex, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
log_error("Framebuffer incomplete\n");
return -1;
}
dst->fbo = fbo;
dst->rbo = 0;
dst->tex = tex;
return 0;
}
void renderer_destroy(struct renderer* self)
{
glDeleteProgram(self->frame_shader.program);
eglMakeCurrent(self->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
destroy_fbo(&self->frame_fbo[1]);
destroy_fbo(&self->frame_fbo[0]);
eglDestroyContext(self->display, self->context);
eglTerminate(self->display);
}
int renderer_init(struct renderer* self, const struct output* output,
enum renderer_input_type input_type)
{
if (!eglBindAPI(EGL_OPENGL_ES_API))
return -1;
if (gl_load_early_extensions() < 0)
return -1;
gl_debug_init();
self->display =
eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY, NULL);
if (!self->display)
return -1;
if (!eglInitialize(self->display, NULL, NULL))
return -1;
static const EGLint cfg_attr[] = {
EGL_SURFACE_TYPE, 0,
EGL_ALPHA_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLConfig cfg;
EGLint cfg_count;
if (!eglChooseConfig(self->display, cfg_attr, &cfg, 1, &cfg_count))
return -1;
static const EGLint ctx_attr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
self->context = eglCreateContext(self->display, cfg, EGL_NO_CONTEXT,
ctx_attr);
if (!self->context)
return -1;
if (!eglMakeCurrent(self->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
self->context))
goto make_current_failure;
log_debug("%s\n", glGetString(GL_VERSION));
if (gl_load_late_extensions() < 0)
goto late_extension_failure;
uint32_t tf_width = output_get_transformed_width(output);
uint32_t tf_height = output_get_transformed_height(output);
if (create_textured_fbo(&self->frame_fbo[0], GL_RGBA, tf_width, tf_height) < 0)
goto frame_fbo_failure_0;
if (create_textured_fbo(&self->frame_fbo[1], GL_RGBA, tf_width, tf_height) < 0)
goto frame_fbo_failure_1;
if (create_fbo(&self->damage_fbo, GL_R8_EXT, UDIV_UP(tf_width, 32), UDIV_UP(tf_height, 32)) < 0)
goto damage_fbo_failure;
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[0].fbo);
switch (input_type) {
case RENDERER_INPUT_DMABUF:
if (gl_compile_shader_program(&self->frame_shader.program,
"dmabuf-vertex.glsl",
"dmabuf-fragment.glsl") < 0)
goto frame_shader_failure;
break;
case RENDERER_INPUT_FB:
if (gl_compile_shader_program(&self->frame_shader.program,
"texture-vertex.glsl",
"texture-fragment.glsl") < 0)
goto frame_shader_failure;
break;
}
if (gl_compile_shader_program(&self->damage_shader.program,
"damage-vertex.glsl",
"damage-fragment.glsl") < 0)
goto damage_shader_failure;
self->frame_shader.u_tex0 =
glGetUniformLocation(self->frame_shader.program, "u_tex0");
self->frame_shader.u_proj =
glGetUniformLocation(self->frame_shader.program, "u_proj");
self->damage_shader.u_tex0 =
glGetUniformLocation(self->damage_shader.program, "u_tex0");
self->damage_shader.u_tex1 =
glGetUniformLocation(self->damage_shader.program, "u_tex1");
self->damage_shader.u_width =
glGetUniformLocation(self->damage_shader.program, "u_width");
self->damage_shader.u_height =
glGetUniformLocation(self->damage_shader.program, "u_height");
self->output = output;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &self->read_format);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &self->read_type);
glViewport(0, 0,
output_get_transformed_width(output),
output_get_transformed_height(output));
gl_clear();
return 0;
damage_shader_failure:
glDeleteShader(self->frame_shader.program);
frame_shader_failure:
destroy_fbo(&self->damage_fbo);
damage_fbo_failure:
destroy_fbo(&self->frame_fbo[1]);
frame_fbo_failure_1:
destroy_fbo(&self->frame_fbo[0]);
frame_fbo_failure_0:
late_extension_failure:
make_current_failure:
eglDestroyContext(self->display, self->context);
return -1;
}
static inline void append_attr(EGLint* dst, int* i, EGLint name, EGLint value)
{
dst[*i] = name;
i[0] += 1;
dst[*i] = value;
i[0] += 1;
}
static void dmabuf_attr_append_planes(EGLint* dst, int* i,
struct dmabuf_frame* frame)
{
#define APPEND_PLANE_ATTR(n) \
if (frame->n_planes <= n) \
return; \
\
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_FD_EXT, frame->plane[n].fd); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_OFFSET_EXT, frame->plane[n].offset); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_PITCH_EXT, frame->plane[n].pitch); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_LO_EXT, frame->plane[n].modifier); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_HI_EXT, frame->plane[n].modifier >> 32); \
APPEND_PLANE_ATTR(0);
APPEND_PLANE_ATTR(1);
APPEND_PLANE_ATTR(2);
APPEND_PLANE_ATTR(3);
#undef APPEND_PLANE_ATTR
}
int render_dmabuf(struct renderer* self, struct dmabuf_frame* frame)
{
int index = 0;
EGLint attr[6 + 10 * 4 + 1];
if (frame->n_planes == 0)
return -1;
append_attr(attr, &index, EGL_WIDTH, frame->width);
append_attr(attr, &index, EGL_HEIGHT, frame->height);
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT, frame->format);
dmabuf_attr_append_planes(attr, &index, frame);
attr[index++] = EGL_NONE;
EGLImageKHR image =
eglCreateImageKHR(self->display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
if (!image)
return -1;
GLuint tex = 0;
glGenTextures(1, &tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
eglDestroyImageKHR(self->display, image);
render_frame(self);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
glDeleteTextures(1, &tex);
return 0;
}
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride)
{
GLuint tex = 0;
glGenTextures(1, &tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
GLenum gl_format;
if (gl_format_from_fourcc(&gl_format, format) < 0)
return -1;
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / 4);
glTexImage2D(GL_TEXTURE_2D, 0, self->read_format, width, height, 0,
gl_format, GL_UNSIGNED_BYTE, addr);
glGenerateMipmap(GL_TEXTURE_2D);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
render_frame(self);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &tex);
return 0;
}
void renderer_read_pixels(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
assert(y + height <= output_get_transformed_height(self->output));
uint32_t width = output_get_transformed_width(self->output);
GLint read_format, read_type;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_format);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);
glReadPixels(0, y, width, height, read_format, read_type, dst);
}
void renderer_read_frame(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[self->frame_index].fbo);
renderer_read_pixels(self, dst, y, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void renderer_read_damage(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
glBindFramebuffer(GL_FRAMEBUFFER, self->damage_fbo.fbo);
uint32_t width = output_get_transformed_width(self->output);
GLint read_format, read_type;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_format);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);
glReadPixels(0, y, UDIV_UP(width, 32), UDIV_UP(height, 32), read_format, read_type, dst);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

View File

@ -23,25 +23,72 @@
#include <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <aml.h>
#include <neatvnc.h>
#include "wlr-screencopy-unstable-v1.h"
#include "buffer.h"
#include "shm.h"
#include "screencopy.h"
#include "smooth.h"
#include "time-util.h"
#include "usdt.h"
#include "pixels.h"
#include "config.h"
#include "render.h"
#define RATE_LIMIT 20.0 // Hz
#define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s
static void screencopy__stop(struct screencopy* self)
static uint32_t fourcc_from_wl_shm(enum wl_shm_format in)
{
aml_stop(aml_get_default(), self->timer);
switch (in) {
case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888;
case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888;
default: return in;
}
}
self->status = SCREENCOPY_STOPPED;
static int screencopy_buffer_init(struct screencopy* self,
enum wl_shm_format format, uint32_t width,
uint32_t height, uint32_t stride)
{
if (self->buffer)
return 0;
size_t size = stride * height;
int fd = shm_alloc_fd(size);
if (fd < 0)
return -1;
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!addr)
goto mmap_failure;
struct wl_shm_pool* pool = wl_shm_create_pool(self->wl_shm, fd, size);
if (!pool)
goto shm_failure;
struct wl_buffer* buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, stride,
format);
wl_shm_pool_destroy(pool);
if (!buffer)
goto shm_failure;
self->buffer = buffer;
self->pixels = addr;
self->bufsize = size;
close(fd);
return 0;
shm_failure:
munmap(addr, size);
mmap_failure:
close(fd);
return -1;
}
static void screencopy_stop(struct frame_capture* fc)
{
struct screencopy* self = (void*)fc;
aml_stop(aml_get_default(), self->timer);
if (self->frame) {
zwlr_screencopy_frame_v1_destroy(self->frame);
@ -49,76 +96,6 @@ static void screencopy__stop(struct screencopy* self)
}
}
void screencopy_stop(struct screencopy* self)
{
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->front = NULL;
return screencopy__stop(self);
}
static void screencopy_linux_dmabuf(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t format, uint32_t width, uint32_t height)
{
#ifdef ENABLE_SCREENCOPY_DMABUF
struct screencopy* self = data;
if (!(wv_buffer_get_available_types() & WV_BUFFER_DMABUF))
return;
self->have_linux_dmabuf = true;
self->dmabuf_width = width;
self->dmabuf_height = height;
self->fourcc = format;
#endif
}
static void screencopy_buffer_done(void* data,
struct zwlr_screencopy_frame_v1* frame)
{
struct screencopy* self = data;
uint32_t width, height, stride, fourcc;
enum wv_buffer_type type = WV_BUFFER_UNSPEC;
#ifdef ENABLE_SCREENCOPY_DMABUF
if (self->have_linux_dmabuf && self->enable_linux_dmabuf) {
width = self->dmabuf_width;
height = self->dmabuf_height;
stride = 0;
fourcc = self->fourcc;
type = WV_BUFFER_DMABUF;
} else
#endif
{
width = self->wl_shm_width;
height = self->wl_shm_height;
stride = self->wl_shm_stride;
fourcc = fourcc_from_wl_shm(self->wl_shm_format);
type = WV_BUFFER_SHM;
}
wv_buffer_pool_resize(self->pool, type, width, height, stride, fourcc);
struct wv_buffer* buffer = wv_buffer_pool_acquire(self->pool);
if (!buffer) {
screencopy__stop(self);
self->status = SCREENCOPY_FATAL;
self->on_done(self);
return;
}
assert(!self->front);
self->front = buffer;
if (self->is_immediate_copy)
zwlr_screencopy_frame_v1_copy(self->frame, buffer->wl_buffer);
else
zwlr_screencopy_frame_v1_copy_with_damage(self->frame,
buffer->wl_buffer);
}
static void screencopy_buffer(void* data,
struct zwlr_screencopy_frame_v1* frame,
enum wl_shm_format format, uint32_t width,
@ -126,61 +103,51 @@ static void screencopy_buffer(void* data,
{
struct screencopy* self = data;
self->wl_shm_format = format;
self->wl_shm_width = width;
self->wl_shm_height = height;
self->wl_shm_stride = stride;
int version = zwlr_screencopy_manager_v1_get_version(self->manager);
if (version < 3) {
self->have_linux_dmabuf = false;
screencopy_buffer_done(data, frame);
return;
if (screencopy_buffer_init(self, format, width, height, stride) < 0) {
self->frame_capture.status = CAPTURE_FATAL;
screencopy_stop(&self->frame_capture);
self->frame_capture.on_done(&self->frame_capture);
}
self->frame_capture.frame_info.fourcc_format =
fourcc_from_wl_shm(format);
self->frame_capture.frame_info.width = width;
self->frame_capture.frame_info.height = height;
self->frame_capture.frame_info.stride = stride;
zwlr_screencopy_frame_v1_copy_with_damage(self->frame, self->buffer);
}
static void screencopy_flags(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t flags)
{
(void)data;
(void)frame;
(void)flags;
struct screencopy* self = data;
self->front->y_inverted =
!!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
/* TODO. Assume y-invert for now */
}
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 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);
screencopy_stop(&self->frame_capture);
self->last_time = gettime_us();
double delay = (self->last_time - self->start_time) * 1.0e-6;
self->delay = smooth(&self->delay_smoother, delay);
if (self->is_immediate_copy)
wv_buffer_damage_whole(self->front);
if (self->back)
wv_buffer_pool_release(self->pool, self->back);
self->back = self->front;
self->front = NULL;
nvnc_fb_set_pts(self->back->nvnc_fb, pts);
self->status = SCREENCOPY_DONE;
self->on_done(self);
self->frame_capture.status = CAPTURE_DONE;
self->frame_capture.on_done(&self->frame_capture);
}
static void screencopy_failed(void* data,
@ -188,16 +155,9 @@ static void screencopy_failed(void* data,
{
struct screencopy* self = data;
DTRACE_PROBE1(wayvnc, screencopy_failed, self);
screencopy__stop(self);
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->front = NULL;
self->status = SCREENCOPY_FAILED;
self->on_done(self);
screencopy_stop(&self->frame_capture);
self->frame_capture.status = CAPTURE_FAILED;
self->frame_capture.on_done(&self->frame_capture);
}
static void screencopy_damage(void* data,
@ -207,19 +167,18 @@ static void screencopy_damage(void* data,
{
struct screencopy* self = data;
DTRACE_PROBE1(wayvnc, screencopy_damage, self);
wv_buffer_damage_rect(self->front, x, y, width, height);
self->frame_capture.damage_hint.x = x;
self->frame_capture.damage_hint.y = y;
self->frame_capture.damage_hint.width = width;
self->frame_capture.damage_hint.height = height;
}
static int screencopy__start_capture(struct screencopy* self)
static int screencopy__start_capture(struct frame_capture* fc)
{
DTRACE_PROBE1(wayvnc, screencopy_start, self);
struct screencopy* self = (void*)fc;
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,
@ -228,8 +187,10 @@ static int screencopy__start_capture(struct screencopy* self)
self->start_time = gettime_us();
self->frame = zwlr_screencopy_manager_v1_capture_output(self->manager,
self->overlay_cursor, self->wl_output);
self->frame =
zwlr_screencopy_manager_v1_capture_output(self->manager,
fc->overlay_cursor,
fc->wl_output);
if (!self->frame)
return -1;
@ -242,50 +203,55 @@ static int screencopy__start_capture(struct screencopy* self)
static void screencopy__poll(void* obj)
{
struct screencopy* self = aml_get_userdata(obj);
struct frame_capture* fc = (struct frame_capture*)self;
screencopy__start_capture(self);
screencopy__start_capture(fc);
}
static int screencopy__start(struct screencopy* self, bool is_immediate_copy)
static int screencopy_start(struct frame_capture* fc)
{
if (self->status == SCREENCOPY_IN_PROGRESS)
return -1;
struct screencopy* self = (void*)fc;
self->is_immediate_copy = is_immediate_copy;
if (fc->status == CAPTURE_IN_PROGRESS)
return -1;
uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6;
int32_t time_left = (1.0 / self->rate_limit - dt - self->delay) * 1.0e6;
double time_left = (1.0 / RATE_LIMIT - dt - self->delay) * 1.0e3;
self->status = SCREENCOPY_IN_PROGRESS;
fc->status = CAPTURE_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);
return screencopy__start_capture(fc);
}
int screencopy_start(struct screencopy* self)
static void screencopy_render(struct frame_capture* fc,
struct renderer* renderer, struct nvnc_fb* fb)
{
return screencopy__start(self, false);
}
struct screencopy* self = (void*)fc;
int screencopy_start_immediate(struct screencopy* self)
{
return screencopy__start(self, true);
uint32_t width = fc->frame_info.width;
uint32_t height = fc->frame_info.height;
uint32_t stride = fc->frame_info.stride;
uint32_t format = fc->frame_info.fourcc_format;
render_framebuffer(renderer, self->pixels, format, width, height, stride);
}
void screencopy_init(struct screencopy* self)
{
self->pool = wv_buffer_pool_create(0, 0, 0, 0, 0);
assert(self->pool);
self->timer = aml_timer_new(0, screencopy__poll, self, NULL);
assert(self->timer);
self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT;
self->frame_capture.backend.start = screencopy_start;
self->frame_capture.backend.stop = screencopy_stop;
self->frame_capture.backend.render = screencopy_render;
}
void screencopy_destroy(struct screencopy* self)
@ -293,13 +259,6 @@ void screencopy_destroy(struct screencopy* self)
aml_stop(aml_get_default(), self->timer);
aml_unref(self->timer);
if (self->back)
wv_buffer_pool_release(self->pool, self->back);
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
self->back = NULL;
self->front = NULL;
wv_buffer_pool_destroy(self->pool);
if (self->buffer)
wl_buffer_destroy(self->buffer);
}

View File

@ -97,17 +97,6 @@ struct seat* seat_find_by_id(struct wl_list* list, uint32_t id)
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;

View File

@ -1,17 +1,5 @@
/*
* 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.
/* The following is based on code puublished under public domain by Drew DeVault
* here: https://wayland-book.com/book/surfaces/shared-memory.html
*/
#include <errno.h>
@ -20,18 +8,6 @@
#include <time.h>
#include <unistd.h>
#include "config.h"
// Linux with glibc < 2.27 has no wrapper
#if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE)
#include <sys/syscall.h>
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;
@ -43,16 +19,9 @@ static void randname(char *buf)
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 {
@ -68,7 +37,6 @@ static int create_shm_file(void)
} while (retries > 0 && errno == EEXIST);
return -1;
#endif
}
int shm_alloc_fd(size_t size)

View File

@ -1,133 +0,0 @@
/*
* 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 <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
static struct table_printer defaults = {
.max_width = 80,
.left_indent = 4,
.column_offset = 8,
.stream = NULL,
.left_width = 0,
};
void table_printer_set_defaults(int max_width, int left_indent,
int column_offset)
{
defaults.max_width = max_width;
defaults.left_indent = left_indent;
defaults.column_offset = column_offset;
}
void table_printer_init(struct table_printer* self, FILE* stream,
int left_width)
{
memcpy(self, &defaults, sizeof(*self));
self->stream = stream;
self->left_width = left_width;
}
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
int width)
{
int line_len = 0;
int last_space_pos = 0;
int dst_len = 0;
int i = 0;
while (true) {
char c = src[i];
if (line_len > width) {
// first word > width
assert(last_space_pos > 0);
// subsequent word > width
assert(dst[last_space_pos] != '\n');
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
}
if (!c)
break;
if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
assert(dst_len < dst_size);
++line_len;
++i;
if (c == '\n')
line_len = 0;
}
dst[dst_len] = '\0';
return dst_len;
}
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
int width, int first_line_indent, int subsequent_indent)
{
char buffer[256];
table_printer_reflow_text(buffer, sizeof(buffer), src, width);
char* line = strtok(buffer, "\n");
fprintf(stream, "%*s%s\n", first_line_indent, "", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", subsequent_indent, "", line);
}
}
void table_printer_print_line(struct table_printer* self, const char* left_text,
const char* right_text)
{
fprintf(self->stream, "%*s", self->left_indent, "");
int field_len = fprintf(self->stream, "%s", left_text);
fprintf(self->stream, "%*s", self->left_width - field_len + self->column_offset, "");
int column_indent = self->left_indent + self->left_width + self->column_offset;
int column_width = self->max_width - column_indent;
table_printer_indent_and_reflow_text(self->stream,
right_text,
column_width, 0, column_indent);
}
void table_printer_print_fmtline(struct table_printer* self,
const char* right_text,
const char* left_format, ...)
{
char buf[64];
va_list args;
va_start(args, left_format);
vsnprintf(buf, sizeof(buf), left_format, args);
va_end(args);
table_printer_print_line(self, buf, right_text);
}

View File

@ -1,236 +0,0 @@
/*
* 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 <stdlib.h>
#include <wayland-client.h>
#include <pixman.h>
/* 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;
}

View File

@ -1,53 +0,0 @@
/*
* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "util.h"
const char* wayvnc_version =
#if defined(PROJECT_VERSION)
PROJECT_VERSION;
#else
"UNKNOWN";
#endif
const char* default_ctl_socket_path()
{
static char buffer[128];
char* xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime)
snprintf(buffer, sizeof(buffer),
"%s/wayvncctl", xdg_runtime);
else
snprintf(buffer, sizeof(buffer),
"/tmp/wayvncctl-%d", getuid());
return buffer;
}
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by)
{
ssize_t remainder = *current_len - advance_by;
if (remainder < 0)
remainder = 0;
else if (remainder > 0)
memmove(*buffer, *buffer + advance_by, remainder);
*current_len = remainder;
}

View File

@ -1,131 +0,0 @@
/*
* Copyright (c) 2022-2023 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <inttypes.h>
#include <errno.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "util.h"
#include "ctl-client.h"
#include "option-parser.h"
#define MAYBE_UNUSED __attribute__((unused))
struct wayvncctl {
bool do_exit;
struct ctl_client* ctl;
};
static int wayvncctl_usage(FILE* stream, struct option_parser* options, int rc)
{
fprintf(stream, "Usage: wayvncctl");
option_parser_print_usage(options, stream);
fprintf(stream, " [parameters]\n");
option_parser_print_cmd_summary(
"Connects to and interacts with a running wayvnc instance.", stream);
option_parser_print_options(options, stream);
fprintf(stream, "\n");
ctl_client_print_command_list(stream);
return rc;
}
static int show_version(void)
{
printf("wayvnc: %s\n", wayvnc_version);
return 0;
}
int main(int argc, char* argv[])
{
struct wayvncctl self = { 0 };
bool verbose = false;
const char* socket_path = NULL;
unsigned flags = 0;
static const struct wv_option opts[] = {
{ .positional = "command",
.is_subcommand = true },
{ 'S', "socket", "<path>",
"Control socket path." },
{ 'w', "wait", NULL,
"Wait for wayvnc to start up if it's not already running." },
{ 'r', "reconnect", NULL,
"If disconnected while waiting for events, wait for wayvnc to restart." },
{ 'j', "json", NULL,
"Output json on stdout." },
{ 'V', "version", NULL,
"Show version info." },
{ 'v', "verbose", NULL,
"Be more verbose." },
{ 'h', "help", NULL,
"Get help (this text)." },
{ }
};
struct option_parser option_parser;
option_parser_init(&option_parser, opts);
if (option_parser_parse(&option_parser, argc,
(const char* const*)argv) < 0)
return wayvncctl_usage(stderr, &option_parser, 1);
if (option_parser_get_value(&option_parser, "help"))
return wayvncctl_usage(stdout, &option_parser, 0);
if (option_parser_get_value(&option_parser, "version"))
return show_version();
socket_path = option_parser_get_value(&option_parser, "socket");
flags |= option_parser_get_value(&option_parser, "wait")
? CTL_CLIENT_SOCKET_WAIT : 0;
flags |= option_parser_get_value(&option_parser, "reconnect")
? CTL_CLIENT_RECONNECT : 0;
flags |= option_parser_get_value(&option_parser, "json")
? CTL_CLIENT_PRINT_JSON : 0;
verbose = !!option_parser_get_value(&option_parser, "verbose");
// No command; nothing to do...
if (!option_parser_get_value(&option_parser, "command"))
return wayvncctl_usage(stdout, &option_parser, 1);
ctl_client_debug_log(verbose);
self.ctl = ctl_client_new(socket_path, &self);
if (!self.ctl)
goto ctl_client_failure;
int result = ctl_client_run_command(self.ctl, &option_parser, flags);
ctl_client_destroy(self.ctl);
return result;
ctl_client_failure:
return 1;
}

View File

@ -1,39 +0,0 @@
# Integration Testing
## Prerequisites
The integration tests currently require that the following tools are installed:
- sway (1.8 or later)
- lsof
- jq
- bash
- vncdotool
Most of these are available in your normal distro package manager, except
vncdotool which is a python tool and installable via pip:
```
pip install vncdotool
```
## Running
```
./test/integration/integration.sh
```
Two test suites are defined:
### Smoke test
Tests basic functionality such as:
- Can wayvnc start and connect to wayland?
- Does the wayvncctl IPC mechanism work (both control and events)?
- Can a VNC client connect and send a keystroke through to sway?
### Multi-output test
Tests wayvnc with a multi-output sway, including:
- Do we detect additions and removals of outputs?
- Do the wayvncctl commands to cycle and switch outputs work?

View File

@ -1,380 +0,0 @@
#!/usr/bin/env bash
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
# Integration test for wayvnc
#
# For now, this doesn't do much, but does check that some basic functionality isn't DOA
#
# Prerequisites:
# - wayvnc and wayvncctl are built in ../build/, or in the $PATH
# - Override by setting $WAYVNC and $WAYVNCCTL or $WAYVNC_BUILD_DIR
# - sway and swaymsg are in the $PATH
# - Override by setting $SWAY and $SWAYMSG
# - jq for parsing json output is in the $PATH
# - lsof for TCP port checking is in the $PATH
# - vncdo for client testing is in the $PATH
# (pip install vncdotool)
set -e
INTEGRATION_ROOT=$(realpath "$(dirname "$0")")
REPO_ROOT=$(realpath "$INTEGRATION_ROOT/../..")
WAYVNC_BUILD_DIR=${WAYVNC_BUILD_DIR:-$(realpath "$REPO_ROOT/build")}
if [[ -d $WAYVNC_BUILD_DIR ]]; then
export PATH=$WAYVNC_BUILD_DIR:$PATH
fi
echo "Looking for required binaries..."
WAYVNC=${WAYVNC:-$(which wayvnc)}
WAYVNCCTL=${WAYVNCCTL:-$(which wayvncctl)}
SWAY=${SWAY:-$(which sway)}
SWAYMSG=${SWAYMSG:-$(which swaymsg)}
echo "Found: $WAYVNC $WAYVNCCTL $SWAY $SWAYMSG"
$WAYVNC --version
$SWAY --version
IFS=" .-" read -r _ _ SWAYMAJOR SWAYMINOR _ < <($SWAY --version)
VNCDO=${VNCDO:-$(which vncdo)}
$VNCDO --version 2>/dev/null
export XDG_CONFIG_HOME=$INTEGRATION_ROOT/xdg_config
export XDG_RUNTIME_DIR=/tmp/wayvnc-integration-$$
test_setup() {
[[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR"
mkdir -p "$XDG_RUNTIME_DIR"
echo "=============================================="
echo "$*"
echo "=============================================="
}
TIMEOUT_COUNTER=0
TIMEOUT_MAXCOUNT=1
TIMEOUT_DELAY=0.1
timeout_init() {
TIMEOUT_COUNTER=0
TIMEOUT_MAXCOUNT=${1:-5}
TIMEOUT_DELAY=${2:-0.1}
}
timeout_check() {
if [[ $(( TIMEOUT_COUNTER++ )) -gt $TIMEOUT_MAXCOUNT ]]; then
return 1
fi
sleep "$TIMEOUT_DELAY"
}
wait_until() {
timeout_init 10
local last
until last=$(eval "$*" 2>&1); do
if ! timeout_check; then
echo "Timeout waiting for $*" >&2
printf "%s\n" "$last" >&2
return 1
fi
done
[[ -z $last ]] || printf "%s\n" "$last"
}
SWAY_ENV=$XDG_RUNTIME_DIR/sway.env
SWAY_PID=
start_sway() {
echo "Starting sway..."
SWAY_LOG=$XDG_RUNTIME_DIR/sway.log
WLR_BACKENDS=headless \
WLR_LIBINPUT_NO_DEVICES=1 \
$SWAY &>"$SWAY_LOG" &
SWAY_PID=$!
wait_until [[ -f "$SWAY_ENV" ]] >/dev/null
WAYLAND_DISPLAY=$(grep ^WAYLAND_DISPLAY= "$SWAY_ENV" | cut -d= -f2-)
SWAYSOCK=$(grep ^SWAYSOCK= "$SWAY_ENV" | cut -d= -f2-)
export WAYLAND_DISPLAY SWAYSOCK
echo " sway is managing $WAYLAND_DISPLAY at $SWAYSOCK"
}
stop_sway() {
[[ -z $SWAY_PID ]] && return 0
echo "Stopping sway ($SWAY_PID)"
kill "$SWAY_PID"
unset SWAY_PID WAYLAND_DISPLAY SWAYSOCK
rm -f "$SWAY_ENV" || true
}
WAYVNC_PID=
WAYVNC_ADDRESS=127.0.0.1
WAYVNC_PORT=5999
start_wayvnc() {
echo "Starting wayvnc..."
WAYVNC_LOG=$XDG_RUNTIME_DIR/wayvnc.log
$WAYVNC "$@" -L debug "$WAYVNC_ADDRESS" "$WAYVNC_PORT" &>$WAYVNC_LOG &
WAYVNC_PID=$!
# Wait for the VNC listening port
echo " Started $WAYVNC_PID"
wait_until lsof -a -p$WAYVNC_PID -iTCP@$WAYVNC_ADDRESS:$WAYVNC_PORT \
-sTCP:LISTEN >/dev/null
echo " Listening on $WAYVNC_ADDRESS:$WAYVNC_PORT"
# Wait for the control socket
wait_until [[ -S "$XDG_RUNTIME_DIR/wayvncctl" ]] >/dev/null
echo " Control socket ready"
}
stop_wayvnc() {
[[ -z $WAYVNC_PID ]] && return 0
echo "Stopping wayvnc ($WAYVNC_PID)"
kill "$WAYVNC_PID"
unset WAYVNC_PID
}
WAYVNCCTL_PID=
WAYVNCCTL_LOG=$XDG_RUNTIME_DIR/wayvncctl.log
WAYVNCCTL_EVENTS=$XDG_RUNTIME_DIR/wayvncctl.events
start_wayvncctl_events() {
$WAYVNCCTL --verbose --wait --reconnect --json event-receive >"$WAYVNCCTL_EVENTS" 2>"$WAYVNCCTL_LOG" &
WAYVNCCTL_PID=$!
}
stop_wayvncctl_events() {
[[ -z $WAYVNCCTL_PID ]] && return 0
echo "Stopping wayvncctl event recorder ($WAYVNCCTL_PID)"
kill "$WAYVNCCTL_PID"
rm -f "$WAYVNCCTL_EVENTS" || true
unset WAYVNCCTL_PID
}
verify_events() {
local expected=("$@")
echo "Verifying recorded events"
local name i=0
while IFS= read -r EVT; do
name=$(jq -r '.method' <<<"$EVT")
ex=${expected[$((i++))]}
echo " Event: $name=~$ex"
[[ $name == "$ex" ]] || return 1
done <"$WAYVNCCTL_EVENTS"
if [[ $i -lt ${#expected[@]} ]]; then
while [[ $i -lt ${#expected[@]} ]]; do
echo " Missing: ${expected[$((i++))]}"
done
return 1
fi
echo "Ok"
}
cleanup() {
result=$?
set +e
stop_wayvnc
stop_sway
stop_wayvncctl_events
if [[ $result != 0 ]]; then
echo
echo SWAY LOG
echo --------
cat "$SWAY_LOG"
echo
echo WAYVNC_LOG
echo ----------
cat "$WAYVNC_LOG"
echo
echo WAYVNCCTL_LOG
echo ----------
cat "$WAYVNCCTL_LOG"
echo
echo VNCDO_LOG
echo ----------
cat "$VNCDO_LOG"
exit
fi
[[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR"
}
trap cleanup EXIT
test_version_ipc() {
echo "Checking version command"
local version
version=$($WAYVNCCTL --json version)
[[ -n $version ]]
echo " version IPC returned data"
echo "ok"
}
sway_active_outputs() {
$SWAYMSG -t get_outputs | jq 'map(select(.active == true))'
}
test_output_list_ipc() {
local expected_capture=${1:-HEADLESS-1}
echo "Checking output-list command"
local sway_json wayvnc_json
sway_json=$(sway_active_outputs)
wayvnc_json=$($WAYVNCCTL --json output-list)
local sway_list wayvnc_list
sway_list=$(jq -r '.[].name' <<<"$sway_json" | sort -u)
wayvnc_list=$(jq -r '.[].name' <<<"$wayvnc_json" | sort -u)
[[ "$sway_list" == "$wayvnc_list" ]]
echo " output-list IPC matches \`swaymsg -t get_outputs\`"
wayvnc_capturing=$(jq -r '.[] | select(.captured == true).name' <<<"$wayvnc_json")
echo " Capturing: $wayvnc_capturing=~$expected_capture"
[[ $wayvnc_capturing == "$expected_capture" ]]
echo "ok"
}
verify_wayvnc_exited() {
wait_until ! kill -0 $WAYVNC_PID >/dev/null
unset WAYVNC_PID
}
test_exit_ipc() {
echo "Checking wayvnc-exit command"
$WAYVNCCTL wayvnc-exit &>/dev/null
verify_wayvnc_exited
echo " wayvnc is shutdown"
echo "ok"
}
client() {
VNCDO_LOG=$XDG_RUNTIME_DIR/vncdo.log
$VNCDO -v --server=$WAYVNC_ADDRESS::$WAYVNC_PORT "$@" &>>$VNCDO_LOG
}
test_client_connect() {
echo "Connecting to send ctrl+t"
client key ctrl-t
echo " Looking for the result..."
[[ -f $XDG_RUNTIME_DIR/test.txt ]]
echo "Ok"
}
output_count() {
sway_active_outputs | jq 'length'
}
sway_output_create() {
local initial_count
initial_count=$(output_count)
echo "Creating new output"
$SWAYMSG create_output &>/dev/null
# shellcheck disable=SC2016
wait_until [[ '$(output_count)' -gt "$initial_count" ]]
echo " $(sway_active_outputs | jq -r '.[-1].name')"
echo "Ok"
}
sway_output_is_gone() {
local output=$1
$SWAYMSG -t get_outputs | jq -e "all(.name != \"$output\")"
}
sway_output_destroy() {
local output=$1
echo "Removing output $output"
$SWAYMSG output "$output" unplug >/dev/null
wait_until sway_output_is_gone "$output" >/dev/null
echo "Ok"
}
smoke_test() {
test_setup "smoke test"
start_sway
start_wayvncctl_events
start_wayvnc
test_version_ipc
wait_until verify_events \
wayvnc-startup
test_output_list_ipc
test_client_connect
wait_until verify_events \
wayvnc-startup \
client-connected \
client-disconnected
test_exit_ipc
wait_until verify_events \
wayvnc-startup \
client-connected \
client-disconnected \
wayvnc-shutdown
stop_wayvncctl_events
stop_sway
}
multioutput_test() {
test_setup "multioutput test"
start_sway
sway_output_create
start_wayvncctl_events
# Ensure outout selection commandline works
start_wayvnc -o HEADLESS-1
wait_until verify_events \
wayvnc-startup
test_output_list_ipc HEADLESS-1
# Test outout-cycle
$WAYVNCCTL output-cycle
wait_until verify_events \
wayvnc-startup \
capture-changed
test_output_list_ipc HEADLESS-2
# Test outout-cycle wraps
$WAYVNCCTL output-cycle
wait_until verify_events \
wayvnc-startup \
capture-changed \
capture-changed
test_output_list_ipc HEADLESS-1
# Add a new output, then switch to it
sway_output_create
wait_until test_output_list_ipc HEADLESS-1
$WAYVNCCTL output-set HEADLESS-3
wait_until verify_events \
wayvnc-startup \
capture-changed \
capture-changed \
capture-changed
test_output_list_ipc HEADLESS-3
if [[ $SWAYMAJOR -le 1 && $SWAYMINOR -lt 8 ]]; then
echo "Warning: sway-1.8 or later is needed for complete testing"
return 0
fi
# Remove the output, and make sure we fallback properly
sway_output_destroy HEADLESS-3
wait_until verify_events \
wayvnc-startup \
capture-changed \
capture-changed \
capture-changed \
capture-changed
wait_until test_output_list_ipc HEADLESS-1
stop_sway
verify_wayvnc_exited
stop_wayvncctl_events
}
smoke_test
#multioutput_test

View File

@ -1,3 +0,0 @@
xwayland disable
bindsym Ctrl+t exec bash -c "echo OK > $XDG_RUNTIME_DIR/test.txt"
exec bash -c "env > $XDG_RUNTIME_DIR/sway.env"

View File

@ -1,18 +0,0 @@
test('table-printer', executable('table-printer',
[
'table-printer-test.c',
'../src/table-printer.c',
],
include_directories: inc,
dependencies: [ ],
))
test('option-parser', executable('option-parser',
[
'option-parser-test.c',
'../src/option-parser.c',
'../src/table-printer.c',
'../src/strlcpy.c',
],
include_directories: inc,
dependencies: [ ],
))

View File

@ -1,314 +0,0 @@
#include "tst.h"
#include "option-parser.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static const struct wv_option options[] = {
{ .positional = "first" },
{ .positional = "second" },
{ .positional = "third" },
{ .positional = "command", .is_subcommand = true },
{ 'a', "option-a", NULL, "Description of a" },
{ 'b', "option-b", NULL, "Description of b" },
{ 'v', "value-option", "value", "Description of v" },
{ },
};
static const struct wv_option default_options[] = {
{ .positional = "first" },
{ .positional = "second", .default_ = "second_default" },
{ 'v', "value-option", "value", "Description of v", .default_ = "v_default" },
{ },
};
static int test_simple(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = {
"executable",
"-a",
"-b",
"pos 1",
"pos 2",
};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
ASSERT_FALSE(option_parser_get_value(&parser, "third"));
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
ASSERT_INT_EQ(0, parser.remaining_argc);
ASSERT_FALSE(parser.remaining_argv);
return 0;
}
static int test_extra_positional_args(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = {
"executable",
"pos 1",
"pos 2",
"-a",
"pos 3",
"-b",
"pos 4",
};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
ASSERT_STR_EQ("pos 3", option_parser_get_value(&parser, "third"));
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
ASSERT_INT_EQ(1, parser.remaining_argc);
ASSERT_STR_EQ("pos 4", parser.remaining_argv[0]);
return 0;
}
static int test_short_value_option_with_space(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-v", "value" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
return 0;
}
static int test_short_value_option_without_space(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-vvalue" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
return 0;
}
static int test_short_value_option_with_eq(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-v=value" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
return 0;
}
static int test_long_value_option_with_space(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "--value-option", "value" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
return 0;
}
static int test_long_value_option_without_space(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "--value-option=value" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
return 0;
}
static int test_multi_short_option(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-ab" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
return 0;
}
static int test_multi_short_option_with_value(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-abvthe-value" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
ASSERT_STR_EQ("the-value", option_parser_get_value(&parser, "v"));
return 0;
}
static int test_stop(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "exec", "-a", "--", "-b"};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_FALSE(option_parser_get_value(&parser, "b"));
ASSERT_INT_EQ(1, parser.remaining_argc);
ASSERT_STR_EQ("-b", parser.remaining_argv[0]);
return 0;
}
static int test_unknown_short_option(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-x" };
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
return 0;
}
static int test_unknown_long_option(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "--an-unknown-long-option" };
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
return 0;
}
static int test_missing_short_value(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-v" };
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
return 0;
}
static int test_missing_long_value(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "--value-option" };
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
return 0;
}
static int test_subcommand_without_arguments(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-ab", "first", "second", "third",
"do-stuff" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
ASSERT_INT_EQ(1, parser.remaining_argc);
ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]);
return 0;
}
static int test_subcommand_with_arguments(void)
{
struct option_parser parser;
option_parser_init(&parser, options);
const char* argv[] = { "executable", "-ab", "first", "second", "third",
"do-stuff", "--some-option", "another-argument"};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
ASSERT_INT_EQ(3, parser.remaining_argc);
ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]);
ASSERT_STR_EQ("another-argument", parser.remaining_argv[2]);
return 0;
}
static int test_defaults_not_set(void)
{
struct option_parser parser;
option_parser_init(&parser, default_options);
const char* argv[] = {
"executable",
"pos 1",
};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
ASSERT_STR_EQ("second_default", option_parser_get_value(&parser, "second"));
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "second"));
ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "value-option"));
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "value-option"));
ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "v"));
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "v"));
ASSERT_INT_EQ(0, parser.remaining_argc);
ASSERT_FALSE(parser.remaining_argv);
return 0;
}
static int test_defaults_overridden(void)
{
struct option_parser parser;
option_parser_init(&parser, default_options);
const char* argv[] = {
"executable",
"pos 1",
"pos 2",
"-v",
"v_set",
};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
ASSERT_STR_EQ("pos 2", option_parser_get_value_no_default(&parser, "second"));
ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "value-option"));
ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "value-option"));
ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "v"));
ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "v"));
ASSERT_INT_EQ(0, parser.remaining_argc);
ASSERT_FALSE(parser.remaining_argv);
return 0;
}
int main()
{
int r = 0;
RUN_TEST(test_simple);
RUN_TEST(test_extra_positional_args);
RUN_TEST(test_short_value_option_with_space);
RUN_TEST(test_short_value_option_without_space);
RUN_TEST(test_short_value_option_with_eq);
RUN_TEST(test_long_value_option_with_space);
RUN_TEST(test_long_value_option_without_space);
RUN_TEST(test_multi_short_option);
RUN_TEST(test_multi_short_option_with_value);
RUN_TEST(test_stop);
RUN_TEST(test_unknown_short_option);
RUN_TEST(test_unknown_long_option);
RUN_TEST(test_missing_short_value);
RUN_TEST(test_missing_long_value);
RUN_TEST(test_subcommand_without_arguments);
RUN_TEST(test_subcommand_with_arguments);
RUN_TEST(test_defaults_not_set);
RUN_TEST(test_defaults_overridden);
return r;
}

View File

@ -1,178 +0,0 @@
#include "tst.h"
#include "table-printer.h"
#include <stdlib.h>
static int test_reflow_text(void)
{
char buf[20];
const char* src = "one two three four";
int len;
len = table_printer_reflow_text(buf, sizeof(buf), src, 20);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 18);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 17);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 10);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 8);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 7);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 6);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 5);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
// width <= 4 cause aborts (if any word length > width)
return 0;
}
static int test_reflow_multiline(void)
{
char buf[20];
const char* src = "one two\nthree four";
table_printer_reflow_text(buf, sizeof(buf), src, 20);
ASSERT_STR_EQ("one two\nthree four", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 18);
ASSERT_STR_EQ("one two\nthree four", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 17);
ASSERT_STR_EQ("one two\nthree four", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 10);
ASSERT_STR_EQ("one two\nthree four", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 9);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 7);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 6);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
table_printer_reflow_text(buf, sizeof(buf), src, 5);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
return 0;
}
static int test_indent_and_reflow(void)
{
size_t len;
char* buf;
FILE* stream;
stream = open_memstream(&buf, &len);
table_printer_indent_and_reflow_text(stream, "one two three four", 7, 2, 4);
fclose(stream);
// strlen(src)=18 + first=2 + subsequent=(2x4) + newline=1
ASSERT_INT_EQ(29, len);
ASSERT_STR_EQ(" one two\n three\n four\n", buf);
free(buf);
return 0;
}
static int test_defaults(void)
{
struct table_printer one;
table_printer_init(&one, stdout, 1);
table_printer_set_defaults(20, 2, 2);
struct table_printer two;
table_printer_init(&two, stderr, 2);
ASSERT_INT_EQ(80, one.max_width);
ASSERT_INT_EQ(4, one.left_indent);
ASSERT_INT_EQ(8, one.column_offset);
ASSERT_INT_EQ(1, one.left_width);
ASSERT_PTR_EQ(stdout, one.stream);
ASSERT_INT_EQ(20, two.max_width);
ASSERT_INT_EQ(2, two.left_indent);
ASSERT_INT_EQ(2, two.column_offset);
ASSERT_INT_EQ(2, two.left_width);
ASSERT_PTR_EQ(stderr, two.stream);
return 0;
}
static int test_print_line(void)
{
size_t len;
char* buf;
struct table_printer printer = {
.max_width = 20,
.left_indent = 2,
.left_width = 6,
.column_offset = 2,
};
printer.stream = open_memstream(&buf, &len);
table_printer_print_line(&printer, "left", "right");
fclose(printer.stream);
ASSERT_STR_EQ(" left right\n", buf);
free(buf);
printer.stream = open_memstream(&buf, &len);
table_printer_print_line(&printer, "left", "right side will wrap");
fclose(printer.stream);
ASSERT_STR_EQ(" left right side\n"
" will wrap\n", buf);
free(buf);
return 0;
}
static int test_print_fmtline(void)
{
size_t len;
char* buf;
struct table_printer printer = {
.max_width = 20,
.left_indent = 2,
.left_width = 6,
.column_offset = 2,
};
printer.stream = open_memstream(&buf, &len);
table_printer_print_fmtline(&printer, "right", "left");
fclose(printer.stream);
ASSERT_STR_EQ(" left right\n", buf);
free(buf);
printer.stream = open_memstream(&buf, &len);
table_printer_print_fmtline(&printer, "right side will wrap", "left%d", 2);
fclose(printer.stream);
ASSERT_STR_EQ(" left2 right side\n"
" will wrap\n", buf);
free(buf);
return 0;
}
int main()
{
int r = 0;
RUN_TEST(test_reflow_text);
RUN_TEST(test_reflow_multiline);
RUN_TEST(test_indent_and_reflow);
RUN_TEST(test_defaults);
RUN_TEST(test_print_line);
RUN_TEST(test_print_fmtline);
return r;
}

View File

@ -1,74 +0,0 @@
#!/usr/bin/python
import os
import math
stream = os.popen('perf script -F time,event')
is_in_update_fb = False
class StateTracker:
def __init__(self, name, src, enter, leave):
self.is_active = False
self.name = name
self.src = src
self.enter = enter
self.leave = leave
self.n = 0
self.dt_sum = 0.0
self.dt_square_sum = 0.0
self.dt_max = 0.0
self.dt_min = math.inf
def add_dt(self, dt):
self.n += 1
self.dt_sum += dt
self.dt_square_sum += dt ** 2
self.dt_max = max(self.dt_max, dt)
self.dt_min = min(self.dt_min, dt)
def apply(self, src, event, t):
if self.is_active:
if (src, event) == (self.src, self.leave):
self.is_active = False
self.add_dt(t - self.t0)
else:
if (src, event) == (self.src, self.enter):
self.is_active = True
self.t0 = t
def avg(self):
return self.dt_sum / self.n
def var(self):
return self.dt_square_sum / self.n - self.avg() ** 2
def stddev(self):
return math.sqrt(self.var())
def report(self):
if self.n == 0:
return
print('{}:'.format(self.name))
print('\tMin, max: {:.1f} ms, {:.1f} ms'.format(self.dt_min * 1e3, self.dt_max * 1e3))
print('\tAverage, std.dev.: {:.1f} ms, {:.1f} ms'.format(self.avg() * 1e3, self.stddev() * 1e3))
trackers = [
StateTracker('Framebuffer update', 'sdt_neatvnc', 'update_fb_start', 'update_fb_done'),
StateTracker('Framebuffer update (only sending)', 'sdt_neatvnc', 'send_fb_start', 'send_fb_done'),
StateTracker('Screencopy', 'sdt_wayvnc', 'screencopy_start', 'screencopy_ready'),
StateTracker('Refine damage', 'sdt_wayvnc', 'refine_damage_start', 'refine_damage_end'),
StateTracker('Render', 'sdt_wayvnc', 'render_start', 'render_end'),
]
for line in stream:
[t, src, event, _] = line.replace(' ', '').split(':')
t = float(t)
for tracker in trackers:
tracker.apply(src, event, t)
for tracker in trackers:
tracker.report()
print()

View File

@ -1,30 +0,0 @@
#!/bin/bash
set -e
EVENTS="sdt_wayvnc:* sdt_neatvnc:*"
delete_all_events()
{
for e in $EVENTS; do
sudo perf probe -d "$e" || true
done
}
add_all_events()
{
for e in $EVENTS; do
sudo perf probe "$e"
done
}
sudo perf buildid-cache -a build/wayvnc
sudo perf buildid-cache -a build/subprojects/neatvnc/libneatvnc.so
delete_all_events
add_all_events
trap "sudo chown $USER:$USER perf.data*" EXIT
sudo perf record -aR -e ${EVENTS// /,}

View File

@ -1,8 +0,0 @@
#!/bin/bash
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
valgrind --leak-check=full \
--show-leak-kinds=all \
--suppressions=$SCRIPT_DIR/valgrind.supp \
$@

View File

@ -1,7 +0,0 @@
{
Ignore dlopen bug.
Memcheck:Leak
...
fun:_dl_*
...
}

View File

@ -1,2 +0,0 @@
auth required pam_unix.so nodelay deny=3 unlock_time=600
account required pam_unix.so nodelay deny=3 unlock_time=600

View File

@ -1,447 +0,0 @@
wayvnc(1)
# NAME
wayvnc - A VNC server for wlroots based Wayland compositors.
# SYNOPSIS
*wayvnc* [options] [address [port]]
# OPTIONS
*-C, --config=<path>*
Select a config file.
*-g,--gpu*
Enable features that require GPU.
*-o, --output=<name>*
Select output to capture.
*-k, --keyboard=<layout>[-variant]*
Select keyboard layout. The variant can be appended if needed.
*-s, --seat=<name>*
Select seat by name.
*-S, --socket=<path>*
Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl
or /tmp/wayvncctl-$UID
*-r, --render-cursor*
Enable overlay cursor rendering.
*-f, --max-fps=<fps>*
Set the rate limit (default 30).
*-p, --show-performance*
Show performance counters.
*-u, --unix-socket*
Create a UNIX domain socket instead of TCP, treating the address as a
path.
*-d, --disable-input*
Disable all remote input. This allows using wayvnc without compositor
support of virtual mouse / keyboard protocols.
*-V, --version*
Show version info.
*-v,--verbose*
Be more verbose. Same as setting `--log-level=info`.
*-w,--websocket*
Create a websocket.
*-L,--log-level*
Set log level. The levels are: error, warning, info, debug, trace and
quiet.
*-h, --help*
Get help.
# DESCRIPTION
This is a VNC server for wlroots based Wayland compositors. It attaches to a
running Wayland session, creates virtual input devices and exposes a single
display via the RFB protocol. The Wayland session may be a headless one, so it
is also possible to run wayvnc without a physical display attached.
## MULTIPLE OUTPUTS
If the Wayland session consists of multiple outputs, only one will be captured.
By default this will be the first one, but can be specified by the _-o_ command
line argument. The argument accepts the short name such as _eDP-1_ or _DP-4_.
Running wayvnc in verbose mode (_-v_) will display the names of all outputs on
startup, or you can query them at runtime via the *wayvncctl output-list*
command.
You can also change which output is being captured on the fly via the *wayvncctl
output-set* command.
# CONFIGURATION
wayvnc searches for a config file in the location
~/$XDG_CONFIG_HOME/wayvnc/config
or if $XDG_CONFIG_HOME is not set
~/.config/wayvnc/config
## SYNTAX
The configuration file is composed of key-value pairs separated with an *equal*
sign. Whitespace around either the key or the value is insignificant and is not
considered to be part of the key or the value.
## KEYWORDS
*address*
The address to which the server shall bind, e.g. 0.0.0.0 or localhost.
*certificate_file*
The path to the certificate file for encryption. Only applicable when
*enable_auth*=true.
*enable_auth*
Enable authentication and encryption. Setting this value to *true*
requires also setting *certificate_file*, *private_key_file*,
*username* and *password*.
*password*
Choose a password for authentication.
*port*
The port to which the server shall bind. Default is 5900.
*private_key_file_file*
The path to the private key file for TLS encryption. Only applicable
when *enable_auth*=true.
*relax_encryption*
Don't require encryption after the user has been authenticated. This
enables some security types such as Apple Diffie-Hellman.
*rsa_private_key_file*
The path to the private key file for RSA-AES encryption. Only applicable
when *enable_auth*=true.
*username*
Choose a username for authentication.
*use_relative_paths*
Make file paths relative to the location of the config file.
*xkb_layout*
The keyboard layout to use for key code lookup.
Default: _XKB_DEFAULT_LAYOUT_ or system default.
*xkb_model*
The keyboard model by which to interpret keycodes and LEDs.
Default: "pc105"
*xkb_options*
A comma separated list of options, through which the user specifies
non-layout related preferences such as which key is the Compose key.
Default: _XKB_DEFAULT_OPTIONS_ or system default.
*xkb_rules*
The rules file describes how to interpret the values of the model,
layout, variant and options fields.
Default: _XKB_DEFAULT_RULES_ or system default.
*xkb_variant*
The keyboard variant to use for keycode lookup.
Default: _XKB_DEFAULT_VARIANT_ or system default.
## EXAMPLE
```
use_relative_paths=true
address=0.0.0.0
enable_auth=true
username=luser
password=p455w0rd
rsa_private_key_file=rsa_key.pem
private_key_file=tls_key.pem
certificate_file=tls_cert.pem
```
# WAYVNCCTL CONTROL SOCKET
To facilitate runtime interaction and control, wayvnc opens a unix domain socket
at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A
client can connect and exchange json-formatted IPC messages to query and control
the running wayvnc instance.
## IPC COMMANDS
_HELP_
The *help* command, when issued without any parameters, lists the names of all
available commands.
If an optional *command* parameter refers to one of those commands by name, the
response data will be a detailed description of that command and its parameters.
_EVENT-RECEIVE_
The *event-receive* command registers for asynchronous server events. See the
_EVENTS_ section below for details on the event message format, and the _IPC
EVENTS_ section below for a description of all possible server events.
Event registration registers for all available server events and is scoped to
the current connection only. If a client disconnects and reconnects, it must
re-register for events.
_CLIENT-LIST_
The *client-list* command retrieves a list of all VNC clients currently
connected to wayvnc.
_CLIENT-DISCONNECT_
The *client-disconnect* command disconnects a single VNC client.
Parameters:
*id*
Required: The ID of the client to disconnect. This ID can be found from the
_GET-CLIENTS_ command or receipt of a _CLIENT-CONNECTED_ event.
_OUTPUT-LIST_
The *output-list* command retrieves a list of all outputs known to wayvnc and
whether or not each one is currently being captured.
_OUTPUT-CYCLE_
For multi-output wayland displays, the *output-cycle* command switches which
output is actively captured by wayvnc. Running this once will switch to the next
available output. If no more outputs are available, it cycles back to the first
again.
_OUTPUT-SET_
For multi-output wayland displays, the *output-set* command switches which
output is actively captured by wayvnc by name.
*output-name=name*
Required: The name of the output to capture next.
_VERSION_
The *version* command queries the running wayvnc instance for its version
information. Much like the _-V_ option, the response data will contain the
version numbers of wayvnc, as well as the versions of the neatvnc and aml
components.
_WAYVNC-EXIT_
The *wayvnc-exit* command disconnects all clients and shuts down wayvnc.
## IPC EVENTS
_CAPTURE_CHANGED_
The *capture-changed* event is sent when the currently captured output
changes.
Parameters:
*output=...*
The name of the output now being captured.
_CLIENT-CONNECTED_
The *client-connected* event is sent when a new VNC client connects to wayvnc.
Parameters:
*id=...*
A unique identifier for this client.
*connection_count=...*
The total number of connected VNC clients including this one.
*address=...*
The IP address of this client. May be null.
*username=...*
The username used to authenticate this client. May be null.
_CLIENT-DISCONNECTED_
The *client-disconnected* event is sent when a VNC cliwnt disconnects from
wayvnc.
Parameters:
*id=...*
A unique identifier for this client.
*connection_count=...*
The total number of connected VNC clients not including this one.
*address=...*
The IP address of this client. May be null.
*username=...*
The username used to authenticate this client. May be null.
## IPC MESSAGE FORMAT
The *wayvncctl(1)* command line utility will construct properly-formatted json
ipc messages, but any client will work. The client initiates the connection and
sends one or more request objects, each of which will receive a corresponding
response object.
*Note* This message format is unstable and may change substantially over the
next few releases.
_REQUEST_
The general form of a json-ipc request message
is:
```
{
"method": "command-name",
"id": 123,
"params": {
"key1": "value1",
"key2": "value2",
}
}
```
The *method* is the name of the command to be executed. Use the *help* method to
query a list of all valid method names.
The *id* field is optional and may be any valid json number or string. If
provided, the response to this request will contain the identical id value,
which the client may use to coordinate multiple requests and responses.
The *params* object supplies optional parameters on a per-method basis, and may
be omitted if empty.
_RESPONSE_
```
{
"id": 123,
"code": 0,
"data": {
...
}
}
```
If the request had an id, the response will have an identical value for *id*.
The numerical *code* provides an indication of how the request was handled. A
value of *0* always signifies success. Any other value means failure, and varies
depending on the method in question.
The *data* object contains method-specific return data. This may be structured
data in response to a query, a simple error string in the case of a failed
request, or it may be omitted entirely if the error code alone is sufficient.
_EVENTS_
Events are aaynchronous messages sent from a server to all registered clients.
The message format is identical to a _REQUEST_, but without an "id" field, and a
client must not send a response.
Example event message:
```
{
"method": "event-name",
"params": {
"key1": "value1",
"key2": "value2",
}
}
```
In order to receive any events, a client must first register to receive them by
sending a _event-receive_ request IPC. Once the success response has been sent
by the server, the client must expect that asynchronous event messages may be
sent by the server at any time, even between a request and the associated
response.
# ENVIRONMENT
The following environment variables have an effect on wayvnc:
_WAYLAND_DISPLAY_
Specifies the name of the Wayland display that the compositor to which
wayvnc shall bind is running on.
_XDG_CONFIG_HOME_
Specifies the location of configuration files.
_XDG_RUNTIME_DIR_
Specifies the default location for the wayvncctl control socket.
# FAQ
*Wayvnc complains that a protocol is not supported*
The error might look like this:
```
wl_registry@2: error 0: invalid version for global zxdg_output_manager_v1 (4): have 2, wanted 3
ERROR: ../src/main.c: 388: Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page.
ERROR: ../src/main.c: 1024: Failed to initialise wayland
```
This means that your wayland compositor does not implement the
screencopy protocol and wayvnc won't work with it. Screencopy is
implemented by wlroots based compositors such as Sway and Wayfire.
*How can I run wayvnc in headless mode/over an SSH session?*
Set the environment variables _WLR_BACKENDS_=headless and
_WLR_LIBINPUT_NO_DEVICES_=1 before starting the compositor, then run
wayvnc as normal.
*How can I pass my mod-key from Sway to the remote desktop session?*
Create an almost empty mode in your sway config. Example:
```
mode passthrough {
bindsym $mod+Pause mode default
}
bindsym $mod+Pause mode passthrough
```
This makes it so that when you press $mod+Pause, all keybindings, except
the one to switch back, are disabled.
*Not all symbols show up when I'm typing. What can I do to fix this?*
Try setting the keyboard layout in wayvnc to the one that most closely
matches the keyboard layout that you're using on the client side. An
exact layout isn't needed, just one that has all the symbols that you
use.
*How do I enable the Compose key?*
Set "xkb_options=compose:menu" in the config file. Any key that is not
otherwise used will work. There just needs to be some key for wayvnc to
match against.
# AUTHORS
Maintained by Andri Yngvason <andri@yngvason.is>. Up-to-date sources can be
found at https://github.com/any1/wayvnc and bugs reports or patches can be
submitted to GitHub's issue tracker.
# SEE ALSO
*wayvncctl(1)*

View File

@ -1,160 +0,0 @@
wayvncctl(1)
# NAME
wayvncctl - A command line control client for wayvnc(1)
# SYNOPSIS
*wayvncctl* [options] [command [--parameter value ...]]
# OPTIONS
*-S, --socket=<path>*
Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl
or /tmp/wayvncctl-$UID
*-w, --wait*
Wait for wayvnc to start up if it's not already running. Default: Exit
immediately with an error if wayvnc is not running.
*-r,--reconnect*
If disconnected while waiting for events, wait for wayvnc to restart and
re-register for events. Default: Exit when wayvnc exits.
*-j, --json*
Produce json output to stdout.
*-V, --version*
Show version info.
*-v,--verbose*
Be more verbose.
*-h, --help*
Get help about the wayvncctl command itself (lists these options). Does
not connect to the wayvncctl control socket.
# DESCRIPTION
*wayvnc(1)* allows runtime interaction via a unix socket json-ipc mechanism.
This command line utility provides easy interaction with those commands.
This command is largely self-documenting:
- Running *wayvncctl --help* lists all supported IPC commands.
- Running *wayvncctl command-name --help* returns a description of the given
command and its available parameters.
- Running *wayvncctl event-receive --help* includes a list of all supported event
names.
- Running *wayvncctl event-receive --show=event-name* returns a
description of the given event and expected data fields.
# ASYNCHRONOUS EVENTS
While *wayvncctl* normally terminates after sending one request and receiving
the corresponding reply, the *event-receive* command acts differently. Instead
of exiting immediately, *wayvncctl* waits for any events from the server,
printing each to stdout as they arrive. This mode of operation will block until
either it receives a signal to terminate, or until the wayvnc server terminates.
In _--json_ mode, each event is printed on one line, with a newline character at
the end, for ease in scripting:
```
$ wayvncctl --json event-receive
{"method":"client-connected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":1}}
{"method":"client-disconnected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":0}}
```
The default human-readible output is a multi-line yaml-like format, with two
newline characters between each event:
```
$ wayvncctl event-receive
client-connected:
id: 0x10ef670
address: 192.168.1.18
connection_count: 1
client-disconnected:
id: 0x10ef670
address: 192.168.1.18
connection_count: 0
```
## SPECIAL LOCAL EVENT TYPES
Especially useful when using _--wait_ or _--reconnect_ mode, wayvncctl will
generate 2 additional events not documented in *wayvnc(1)*:
*wayvnc-startup*
Sent when a successful wayvnc control connection is established and
event registration has succeeded, both upon initial startup and on
subsequent registrations with _--reconnect_.
No paramerers.
*wayvnc-shutdown*
Sent when the wayvnc control connection is dropped, usually due to
wayvnc exiting.
No paramerers.
# EXAMPLES
Get help on the "output-set" IPC command:
```
$ wayvncctl output-set --help
Usage: wayvncctl [options] output-set <output-name> [params]
...
```
Cycle to the next active output:
```
$ wayvncctl output-cycle
```
Get json-formatted version information:
```
$ wayvncctl --json version
{"wayvnc":"v0.5.0","neatvnc":"v0.5.1","aml":"v0.2.2"}
```
A script that takes an action for each client connect and disconnect event:
```
#!/bin/bash
connection_count_now() {
echo "Total clients: $1"
}
while IFS= read -r EVT; do
case "$(jq -r '.method' <<<"$EVT")" in
client-*onnected)
count=$(jq -r '.params.connection_count' <<<"$EVT")
connection_count_now "$count"
;;
wayvnc-shutdown)
connection_count_now 0
;;
esac
done < <(wayvncctl --wait --reconnect --json event-receive)
```
# ENVIRONMENT
The following environment variables have an effect on wayvncctl:
_XDG_RUNTIME_DIR_
Specifies the default location for the wayvncctl control socket.
# SEE ALSO
*wayvnc(1)*