Compare commits

..

1 Commits

Author SHA1 Message Date
Andri Yngvason 6556d893a8 Implement custom option parser 2023-01-01 09:10:31 +00:00
58 changed files with 827 additions and 4402 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,14 +1,16 @@
name: Build and Unit Test name: Build check
on: on:
push: push:
branches: [ "master", "ci-test" ] branches: [ "master" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
jobs: jobs:
check: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: checkout aml - name: checkout aml
@ -21,18 +23,9 @@ jobs:
with: with:
repository: any1/neatvnc repository: any1/neatvnc
path: subprojects/neatvnc path: subprojects/neatvnc
- name: prepare environment - name: setup
run: | run: sudo apt install -y meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev libavutil-dev libturbojpeg0-dev scdoc
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 - name: configure
run: meson build -D tests=true run: meson build
- name: compile - name: make
run: meson compile -C build run: ninja -C build
- name: unit tests
run: meson test --verbose -C build
- name: integration tests
run: ./test/integration/integration.sh

1
.gitignore vendored
View File

@ -7,4 +7,3 @@ perf.*
*.pem *.pem
.vimrc .vimrc
.cache .cache
.vscode

View File

@ -3,8 +3,7 @@
## Commit Messages ## Commit Messages
Please, try to write good commit messages. Do your best to follow these 7 rules, 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 borrowed from [Chris Beams](https://chris.beams.io/posts/git-commit/):
extra rule:
1. Separate subject from body with a blank line 1. Separate subject from body with a blank line
2. Limit the subject line to 50 characters 2. Limit the subject line to 50 characters
@ -13,36 +12,15 @@ extra rule:
5. Use the imperative mood in the subject line 5. Use the imperative mood in the subject line
6. Wrap the body at 72 characters 6. Wrap the body at 72 characters
7. Use the body to explain what and why vs. how 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 If you wish to know why we follow these rules, please read Chris Beams' blog
entry, linked above. 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 ## Style
This project follows the the This project follows the the
[Linux kernel's style guide](https://www.kernel.org/doc/html/latest/process/coding-style.html#codingstyle) [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: as far as coding style is concererned, with the following exceptions:
* When declaring pointer variables, the asterisk (`*`) is placed on the left * When declaring pointer variables, the asterisk (`*`) is placed on the left
with the type rather than the variable name. Declaring multiple variables in with the type rather than the variable name. Declaring multiple variables in
@ -51,135 +29,7 @@ as far as coding style is concerned, with the following exceptions:
a lot of code that uses aligned argument lists in the project, but I have 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. 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 ## No Brown M&Ms
All pull requests must contain the following sentence in the description: All pull requests must contain the following sentence in the description:
I have read and understood CONTRIBUTING.md. I have read and understood CONTRIBUTING.md and its associated documents.

10
FAQ.md
View File

@ -19,16 +19,6 @@ bindsym $mod+Pause mode passthrough
This makes it so that when you press $mod+Pause, all keybindings, except the one This makes it so that when you press $mod+Pause, all keybindings, except the one
to switch back, are disabled. to switch back, are disabled.
Disable `floating_modifier` during the mode if it's set up in your config file
and you wish to be able to use the same functionality in the nested desktop:
```
mode passthrough {
bindsym $mod+Pause mode default; floating_modifier $mod normal
}
bindsym $mod+Pause mode passthrough; floating_modifier none
```
Replace `$mod normal` with different arguments if applicable.
**Q: Not all symbols show up when I'm typing. What can I do to fix this?** **Q: Not all symbols show up when I'm typing. What can I do to fix this?**
A: Try setting the keyboard layout in wayvnc to the one that most closely A: Try setting the keyboard layout in wayvnc to the one that most closely

View File

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

129
README.md
View File

@ -1,9 +1,5 @@
# wayvnc # 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 ## Introduction
This is a VNC server for wlroots-based Wayland compositors (:no_entry: Gnome, 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, KDE and Weston are **not** supported). It attaches to a running Wayland session,
@ -16,6 +12,27 @@ support, join the #wayvnc IRC channel on libera.chat, or ask your questions on t
GitHub [discussion forum](https://github.com/any1/wayvnc/discussions) for the GitHub [discussion forum](https://github.com/any1/wayvnc/discussions) for the
project. project.
## Installing
```
# Arch Linux
pacman -S wayvnc
# FreeBSD
pkg install wayvnc
# Fedora
dnf install wayvnc
# Debian (unstable / testing)
apt install wayvnc
# openSUSE Tumbleweed
zypper install wayvnc
# Void Linux
xbps-install wayvnc
```
## Building ## Building
### Runtime Dependencies ### Runtime Dependencies
* aml * aml
@ -38,15 +55,13 @@ project.
pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson
``` ```
#### For Fedora 37 #### For Fedora 31
``` ```
dnf install -y meson gcc ninja-build pkg-config egl-wayland egl-wayland-devel \ dnf install -y meson gcc ninja-build pkg-config egl-wayland egl-wayland-devel \
mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \ mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \
libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \ libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \
libxkbcommon-devel libxkbcommon libwayland-client \ libxkbcommon-devel libxkbcommon libwayland-client libwayland \
pam-devel pixman-devel libgbm-devel libdrm-devel scdoc \ wayland-devel gnutls-devel jansson-devel
libavcodec-free-devel libavfilter-free-devel libavutil-free-devel \
turbojpeg-devel wayland-devel gnutls-devel jansson-devel
``` ```
#### For Debian (unstable / testing) #### For Debian (unstable / testing)
@ -88,16 +103,6 @@ meson build
ninja -C build ninja -C build
``` ```
To run the unit tests:
```
meson test -C build
```
To run the [integration tests](test/integration/README.md):
```
./test/integration/integration.sh
```
## Running ## Running
Wayvnc can be run from the build directory like so: Wayvnc can be run from the build directory like so:
``` ```
@ -116,17 +121,12 @@ use SSH tunneling while listening on localhost, but users can also be
authenticated when connecting to wayvnc. authenticated when connecting to wayvnc.
### Encryption & Authentication ### Encryption & Authentication
You'll need a private X509 key and a certificate. A self-signed key with a
#### VeNCrypt (TLS) certificate can be generated like so:
For TLS, you'll need a private X509 key and a certificate. A self-signed key
with a certificate can be generated like so:
``` ```
cd ~/.config/wayvnc openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -sha384 \ -keyout key.pem -out cert.pem -subj /CN=localhost \
-days 3650 -nodes -keyout tls_key.pem -out tls_cert.pem \
-subj /CN=localhost \
-addext subjectAltName=DNS:localhost,DNS:localhost,IP:127.0.0.1 -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 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 host name and IP address, respectively, or just keep them as is if you're
@ -136,42 +136,14 @@ Create a config with the authentication info and load it using the `--config`
command line option or place it at the default location command line option or place it at the default location
`$HOME/.config/wayvnc/config`. `$HOME/.config/wayvnc/config`.
``` ```
use_relative_paths=true
address=0.0.0.0 address=0.0.0.0
enable_auth=true enable_auth=true
username=luser username=luser
password=p455w0rd password=p455w0rd
private_key_file=tls_key.pem private_key_file=/path/to/key.pem
certificate_file=tls_cert.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 ### wayvncctl control socket
To facilitate runtime interaction and control, wayvnc opens a unix domain socket To facilitate runtime interaction and control, wayvnc opens a unix domain socket
@ -182,6 +154,47 @@ the running wayvnc instance.
Use the `wayvncctl` utility to interact with this control socket from the Use the `wayvncctl` utility to interact with this control socket from the
command line. command line.
The `help` command can interactively query the available IPC commands:
```
$ wayvncctl help
Commands:
- help
- version
- event-receive
- set-output
- get-clients
- get-outputs
- disconnect-client
- wayvnc-exit
Run 'wayvncctl command-name --help' for command-specific details.
Events:
- client-connected
- client-disconnected
- capture-changed
Run 'wayvncctl help --event=event-name' for event-specific details.
```
And give descriptions and usage for specific commands:
```
$ wayvncctl set-output --help
Usage: wayvncctl [options] set-output [params]
Switch the actively captured output
Parameters:
--switch-to=...
The specific output name to capture
--cycle=...
Either "next" or "prev"
Run 'wayvncctl --help' for allowed options
```
See the `wayvnc(1)` manpage for an in-depth description of the IPC protocol and 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 the available commands, and `wayvncctl(1)` for more on the command line
interface. interface.

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,29 +1,4 @@
#!/bin/bash #!/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} WAYVNCCTL=${WAYVNCCTL:-wayvncctl}

View File

@ -1,29 +1,4 @@
#!/bin/bash #!/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} WAYVNCCTL=${WAYVNCCTL:-wayvncctl}
SWAYMSG=${SWAYMSG:-swaymsg} SWAYMSG=${SWAYMSG:-swaymsg}
@ -39,7 +14,7 @@ fi
find_output_matching() { find_output_matching() {
local pattern=$1 local pattern=$1
$WAYVNCCTL -j output-list | jq -r ".[].name | match(\"$pattern\").string" $WAYVNCCTL -j get-outputs | jq -r ".[].name | match(\"$pattern\").string"
} }
wait_for_output_matching() { wait_for_output_matching() {
@ -66,7 +41,7 @@ restore_outputs() {
local firstOutput=${OUTPUTS_TO_RECONNECT[0]} local firstOutput=${OUTPUTS_TO_RECONNECT[0]}
echo "Switching wayvnc back to physical output $firstOutput" echo "Switching wayvnc back to physical output $firstOutput"
wait_for_output_matching "$firstOutput" >/dev/null wait_for_output_matching "$firstOutput" >/dev/null
$WAYVNCCTL output-set "$firstOutput" $WAYVNCCTL set-output --switch-to="$firstOutput"
echo "Removing virtual output $HEADLESS" echo "Removing virtual output $HEADLESS"
$SWAYMSG output "$HEADLESS" unplug $SWAYMSG output "$HEADLESS" unplug
fi fi
@ -80,17 +55,17 @@ collapse_outputs() {
local preexisting="$(find_output_matching 'HEADLESS-\\d+')" local preexisting="$(find_output_matching 'HEADLESS-\\d+')"
if [[ $preexisting ]]; then if [[ $preexisting ]]; then
echo "Switching to preexisting virtual output $preexisting" echo "Switching to preexisting virtual output $preexisting"
$WAYVNCCTL output-set "$preexisting" $WAYVNCCTL set-output --switch-to="$preexisting"
else else
echo "Creating a virtual display" echo "Creating a virtual display"
$SWAYMSG create_output $SWAYMSG create_output
echo "Waiting for virtusl output to be created..." echo "Waiting for virtusl output to be created..."
HEADLESS=$(wait_for_output_matching 'HEADLESS-\\d+') HEADLESS=$(wait_for_output_matching 'HEADLESS-\\d+')
echo "Switching to virtual output $HEADLESS" echo "Switching to virtual output $HEADLESS"
$WAYVNCCTL output-set "$HEADLESS" $WAYVNCCTL set-output --switch-to="$HEADLESS"
fi fi
fi fi
for output in $($WAYVNCCTL -j output-list | jq -r '.[] | select(.captured==false).name'); do for output in $($WAYVNCCTL -j get-outputs | jq -r '.[] | select(.captured==false).name'); do
echo "Disabling extra output $output" echo "Disabling extra output $output"
$SWAYMSG output "$output" disable $SWAYMSG output "$output" disable
OUTPUTS_TO_RECONNECT+=("$output") OUTPUTS_TO_RECONNECT+=("$output")

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 * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -21,10 +21,8 @@
#define X_CFG_LIST \ #define X_CFG_LIST \
X(bool, enable_auth) \ X(bool, enable_auth) \
X(bool, relax_encryption) \
X(string, private_key_file) \ X(string, private_key_file) \
X(string, certificate_file) \ X(string, certificate_file) \
X(string, rsa_private_key_file) \
X(string, username) \ X(string, username) \
X(string, password) \ X(string, password) \
X(string, address) \ X(string, address) \
@ -35,10 +33,8 @@
X(string, xkb_layout) \ X(string, xkb_layout) \
X(string, xkb_variant) \ X(string, xkb_variant) \
X(string, xkb_options) \ X(string, xkb_options) \
X(bool, use_relative_paths) \
struct cfg { struct cfg {
char* directory;
#define string char* #define string char*
#define uint uint32_t #define uint uint32_t
#define X(type, name) type name; #define X(type, name) type name;

View File

@ -20,7 +20,6 @@
#include <stdbool.h> #include <stdbool.h>
struct ctl_client; struct ctl_client;
struct option_parser;
void ctl_client_debug_log(bool enable); void ctl_client_debug_log(bool enable);
@ -33,7 +32,7 @@ void* ctl_client_userdata(struct ctl_client*);
#define CTL_CLIENT_RECONNECT (1 << 2) #define CTL_CLIENT_RECONNECT (1 << 2)
int ctl_client_run_command(struct ctl_client* self, int ctl_client_run_command(struct ctl_client* self,
struct option_parser* parent_options, unsigned flags); int argc, char* argv[], unsigned flags);
void ctl_client_print_command_list(FILE* stream); void ctl_client_print_command_list(FILE* stream);
void ctl_client_print_event_list(FILE* stream); void ctl_client_print_event_list(FILE* stream);

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -17,31 +16,23 @@
#pragma once #pragma once
#include <stdbool.h>
enum cmd_type { enum cmd_type {
CMD_ATTACH,
CMD_DETACH,
CMD_HELP, CMD_HELP,
CMD_EVENT_RECEIVE,
CMD_CLIENT_LIST,
CMD_CLIENT_DISCONNECT,
CMD_OUTPUT_LIST,
CMD_OUTPUT_CYCLE,
CMD_OUTPUT_SET,
CMD_VERSION, CMD_VERSION,
CMD_EVENT_RECEIVE,
CMD_SET_OUTPUT,
CMD_GET_CLIENTS,
CMD_GET_OUTPUTS,
CMD_DISCONNECT_CLIENT,
CMD_WAYVNC_EXIT, CMD_WAYVNC_EXIT,
CMD_UNKNOWN, CMD_UNKNOWN,
}; };
#define CMD_LIST_LEN CMD_UNKNOWN #define CMD_LIST_LEN CMD_UNKNOWN
enum event_type { enum event_type {
EVT_CAPTURE_CHANGED,
EVT_CLIENT_CONNECTED, EVT_CLIENT_CONNECTED,
EVT_CLIENT_DISCONNECTED, EVT_CLIENT_DISCONNECTED,
EVT_DETACHED, EVT_CAPTURE_CHANGED,
EVT_OUTPUT_ADDED,
EVT_OUTPUT_REMOVED,
EVT_UNKNOWN, EVT_UNKNOWN,
}; };
#define EVT_LIST_LEN EVT_UNKNOWN #define EVT_LIST_LEN EVT_UNKNOWN
@ -49,8 +40,6 @@ enum event_type {
struct cmd_param_info { struct cmd_param_info {
char* name; char* name;
char* description; char* description;
char* schema;
bool positional;
}; };
struct cmd_info { struct cmd_info {

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2022 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -19,21 +18,13 @@
#include "output.h" #include "output.h"
#include <sys/socket.h>
struct ctl; struct ctl;
struct cmd_response; struct cmd_response;
struct ctl_server_client; struct ctl_server_vnc_client {
char id[64];
struct ctl_server_client_info { char hostname[256];
int id; char username[256];
union {
struct sockaddr_storage address_storage;
struct sockaddr address;
};
const char* username;
const char* seat;
}; };
struct ctl_server_output { struct ctl_server_output {
@ -47,8 +38,6 @@ struct ctl_server_output {
struct ctl_server_actions { struct ctl_server_actions {
void* userdata; 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*, struct cmd_response* (*on_output_cycle)(struct ctl*,
enum output_cycle_direction direction); enum output_cycle_direction direction);
struct cmd_response* (*on_output_switch)(struct ctl*, struct cmd_response* (*on_output_switch)(struct ctl*,
@ -57,10 +46,11 @@ struct ctl_server_actions {
const char* id); const char* id);
struct cmd_response* (*on_wayvnc_exit)(struct ctl*); struct cmd_response* (*on_wayvnc_exit)(struct ctl*);
struct ctl_server_client *(*client_next)(struct ctl*, // Return number of elements created
struct ctl_server_client* prev); // Allocate 'clients' array or set ton ULL if none
void (*client_info)(const struct ctl_server_client*, // Receiver will free(clients) when done.
struct ctl_server_client_info* info); int (*get_client_list)(struct ctl*,
struct ctl_server_vnc_client** clients);
// Return number of elements created // Return number of elements created
// Allocate 'outputs' array or set to NULL if none // Allocate 'outputs' array or set to NULL if none
@ -78,17 +68,16 @@ struct cmd_response* cmd_ok(void);
struct cmd_response* cmd_failed(const char* fmt, ...); struct cmd_response* cmd_failed(const char* fmt, ...);
void ctl_server_event_connected(struct ctl*, void ctl_server_event_connected(struct ctl*,
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count); int new_connection_count);
void ctl_server_event_disconnected(struct ctl*, void ctl_server_event_disconnected(struct ctl*,
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count); int new_connection_count);
void ctl_server_event_capture_changed(struct ctl*, void ctl_server_event_capture_changed(struct ctl*,
const char* captured_output); 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

@ -19,13 +19,11 @@
#include <stdlib.h> #include <stdlib.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <stdbool.h> #include <stdbool.h>
#include <neatvnc.h>
#include "intset.h" #include "intset.h"
struct zwp_virtual_keyboard_v1; struct zwp_virtual_keyboard_v1;
struct table_entry; struct table_entry;
struct nvnc;
struct keyboard { struct keyboard {
struct zwp_virtual_keyboard_v1* virtual_keyboard; struct zwp_virtual_keyboard_v1* virtual_keyboard;
@ -46,4 +44,3 @@ void keyboard_destroy(struct keyboard* self);
void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed); void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed);
void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code, void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code,
bool is_pressed); bool is_pressed);
enum nvnc_keyboard_led_state keyboard_get_led_state(const struct keyboard*);

View File

@ -35,7 +35,6 @@ struct wv_option_value {
}; };
struct option_parser { struct option_parser {
const char* name;
const struct wv_option* options; const struct wv_option* options;
int n_opts; int n_opts;
@ -43,25 +42,15 @@ struct option_parser {
int n_values; int n_values;
int position; int position;
size_t remaining_argc; int endpos;
const char* const* remaining_argv;
}; };
void option_parser_init(struct option_parser* self, void option_parser_init(struct option_parser* self,
const struct wv_option* options); 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); void option_parser_print_options(struct option_parser* self, FILE* stream);
int option_parser_parse(struct option_parser* self, int argc, int option_parser_parse(struct option_parser* self, int argc,
const char* const* argv); const char* const* argv);
const char* option_parser_get_value(const struct option_parser* self, const char* option_parser_get_value(const struct option_parser* self,
const char* name); 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

@ -1,28 +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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct output;
struct zwlr_output_manager_v1;
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager);
bool wlr_output_manager_resize_output(struct output* output,
uint16_t width, uint16_t height);
void wlr_output_manager_destroy(void);

View File

@ -57,7 +57,6 @@ struct output {
bool is_dimension_changed; bool is_dimension_changed;
bool is_transform_changed; bool is_transform_changed;
bool is_headless;
void (*on_dimension_change)(struct output*); void (*on_dimension_change)(struct output*);
void (*on_transform_change)(struct output*); void (*on_transform_change)(struct output*);

View File

@ -22,4 +22,3 @@
enum wl_shm_format fourcc_to_wl_shm(uint32_t in); enum wl_shm_format fourcc_to_wl_shm(uint32_t in);
uint32_t fourcc_from_wl_shm(enum wl_shm_format in); uint32_t fourcc_from_wl_shm(enum wl_shm_format in);
int pixel_size_from_fourcc(uint32_t fourcc);

View File

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

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,7 +19,6 @@
#include <sys/types.h> #include <sys/types.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b)) #define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(a, b) ((b) * UDIV_UP((a), (b)))
extern const char* wayvnc_version; extern const char* wayvnc_version;

View File

@ -1,7 +1,7 @@
project( project(
'wayvnc', 'wayvnc',
'c', 'c',
version: '0.9-dev', version: '0.5.0',
license: 'ISC', license: 'ISC',
default_options: [ default_options: [
'c_std=gnu11', 'c_std=gnu11',
@ -14,30 +14,26 @@ host_system = host_machine.system()
prefix = get_option('prefix') prefix = get_option('prefix')
c_args = [ c_args = [
'-DPROJECT_VERSION="@0@"'.format(meson.project_version()),
'-D_GNU_SOURCE', '-D_GNU_SOURCE',
'-DAML_UNSTABLE_API=1', '-DAML_UNSTABLE_API=1',
'-DWLR_USE_UNSTABLE=true',
'-Wno-unused-parameter', '-Wno-unused-parameter',
'-Wno-missing-field-initializers', '-Wno-missing-field-initializers',
] ]
version = '"@0@"'.format(meson.project_version())
git = find_program('git', native: true, required: false) git = find_program('git', native: true, required: false)
if git.found() if git.found()
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false) git_describe = run_command([git, 'describe', '--tags', '--long'])
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'])
if git_commit.returncode() == 0 and git_branch.returncode() == 0 if git_describe.returncode() == 0 and git_branch.returncode() == 0
version = '"v@0@-@1@ (@2@)"'.format( c_args += '-DGIT_VERSION="@0@ (@1@)"'.format(
meson.project_version(), git_describe.stdout().strip(),
git_commit.stdout().strip(),
git_branch.stdout().strip(), git_branch.stdout().strip(),
) )
endif endif
endif endif
add_project_arguments('-DPROJECT_VERSION=@0@'.format(version), language: 'c')
if buildtype != 'debug' and buildtype != 'debugoptimized' if buildtype != 'debug' and buildtype != 'debugoptimized'
c_args += '-DNDEBUG' c_args += '-DNDEBUG'
endif endif
@ -54,18 +50,10 @@ pixman = dependency('pixman-1')
gbm = dependency('gbm', required: get_option('screencopy-dmabuf')) gbm = dependency('gbm', required: get_option('screencopy-dmabuf'))
drm = dependency('libdrm') drm = dependency('libdrm')
xkbcommon = dependency('xkbcommon', version: '>=1.0.0') xkbcommon = dependency('xkbcommon', version: '>=1.0.0')
wayland_server = dependency('wayland-server')
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_client_protocol = dependency('wayland-protocols')
wayland_cursor = dependency('wayland-cursor')
jansson = dependency('jansson') jansson = dependency('jansson')
# Cursor image neatvnc_version = '>=0.5.0'
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_project = subproject(
'neatvnc', 'neatvnc',
@ -73,11 +61,11 @@ neatvnc_project = subproject(
version: neatvnc_version, version: neatvnc_version,
) )
aml_project = subproject('aml', required: false, version: aml_version) aml_project = subproject('aml', required: false)
if aml_project.found() if aml_project.found()
aml = aml_project.get_variable('aml_dep') aml = aml_project.get_variable('aml_dep')
else else
aml = dependency('aml', version: aml_version) aml = dependency('aml')
endif endif
if neatvnc_project.found() if neatvnc_project.found()
@ -86,7 +74,7 @@ else
neatvnc = dependency('neatvnc', version: neatvnc_version) neatvnc = dependency('neatvnc', version: neatvnc_version)
endif endif
inc = include_directories('include', '/usr/include/wlroots0.16') inc = include_directories('include')
subdir('protocols') subdir('protocols')
@ -97,7 +85,6 @@ sources = [
'src/screencopy.c', 'src/screencopy.c',
'src/data-control.c', 'src/data-control.c',
'src/output.c', 'src/output.c',
'src/output-management.c',
'src/pointer.c', 'src/pointer.c',
'src/keyboard.c', 'src/keyboard.c',
'src/seat.c', 'src/seat.c',
@ -112,7 +99,6 @@ sources = [
'src/ctl-server.c', 'src/ctl-server.c',
'src/ctl-commands.c', 'src/ctl-commands.c',
'src/option-parser.c', 'src/option-parser.c',
'src/table-printer.c',
] ]
dependencies = [ dependencies = [
@ -127,11 +113,6 @@ dependencies = [
xkbcommon, xkbcommon,
client_protos, client_protos,
jansson, jansson,
x11_dep,
x11_fixes_dep,
wayland_client_protocol,
wayland_cursor,
wayland_server
] ]
ctlsources = [ ctlsources = [
@ -142,7 +123,6 @@ ctlsources = [
'src/ctl-commands.c', 'src/ctl-commands.c',
'src/strlcpy.c', 'src/strlcpy.c',
'src/option-parser.c', 'src/option-parser.c',
'src/table-printer.c',
] ]
ctldependencies = [ ctldependencies = [
@ -157,10 +137,6 @@ if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt
config.set('HAVE_USDT', true) config.set('HAVE_USDT', true)
endif 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') if cc.has_function('memfd_create')
config.set('HAVE_MEMFD', true) config.set('HAVE_MEMFD', true)
config.set('HAVE_MEMFD_CREATE', true) config.set('HAVE_MEMFD_CREATE', true)

View File

@ -6,5 +6,5 @@ option('man-pages', type: 'feature', value: 'auto',
description: 'Generate and install man pages') description: 'Generate and install man pages')
option('systemtap', type: 'boolean', value: false, option('systemtap', type: 'boolean', value: false,
description: 'Enable tracing using sdt') description: 'Enable tracing using sdt')
option('tests', type: 'boolean', value: true, option('tests', type: 'boolean', value: false,
description: 'Build unit tests') 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

@ -21,9 +21,7 @@ client_protocols = [
'xdg-output-unstable-v1.xml', 'xdg-output-unstable-v1.xml',
'linux-dmabuf-unstable-v1.xml', 'linux-dmabuf-unstable-v1.xml',
'wlr-data-control-unstable-v1.xml', 'wlr-data-control-unstable-v1.xml',
'wlr-output-management-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml',
'ext-transient-seat-v1.xml',
] ]
client_protos_src = [] client_protos_src = []

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,5 +1,5 @@
/* /*
* Copyright (c) 2020 - 2024 Andri Yngvason * Copyright (c) 2020 - 2021 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -31,20 +31,10 @@
#include "buffer.h" #include "buffer.h"
#include "pixels.h" #include "pixels.h"
#include "config.h" #include "config.h"
#include "util.h"
#ifdef ENABLE_SCREENCOPY_DMABUF #ifdef ENABLE_SCREENCOPY_DMABUF
#include <gbm.h> #include <gbm.h>
#include <sys/ioctl.h> #endif
#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 wl_shm* wl_shm;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf; extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
@ -101,10 +91,9 @@ struct wv_buffer* wv_buffer_create_shm(int width,
if (!self->wl_buffer) if (!self->wl_buffer)
goto shm_failure; goto shm_failure;
int bpp = pixel_size_from_fourcc(fourcc); // TODO: Get the pixel size from the format instead of assuming it's 4.
assert(bpp > 0);
self->nvnc_fb = nvnc_fb_from_buffer(self->pixels, width, height, fourcc, self->nvnc_fb = nvnc_fb_from_buffer(self->pixels, width, height, fourcc,
stride / bpp); stride / 4);
if (!self->nvnc_fb) { if (!self->nvnc_fb) {
goto nvnc_fb_failure; goto nvnc_fb_failure;
} }
@ -128,81 +117,6 @@ failure:
} }
#ifdef ENABLE_SCREENCOPY_DMABUF #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, static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
uint32_t fourcc) uint32_t fourcc)
{ {
@ -218,17 +132,8 @@ static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
self->height = height; self->height = height;
self->format = fourcc; self->format = fourcc;
// Checks not needed anymore. Fixed with SCANOUT and within neatvnc for most GPUs. self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
// But this could still fail! GBM_BO_USE_RENDERING);
//#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) if (!self->bo)
goto bo_failure; goto bo_failure;
@ -407,8 +312,8 @@ static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool,
return false; return false;
} }
#ifdef ENABLE_SCREENCOPY_DMABUF
/* fall-through */ /* fall-through */
#ifdef ENABLE_SCREENCOPY_DMABUF
case WV_BUFFER_DMABUF: case WV_BUFFER_DMABUF:
#endif #endif
if (pool->width != buffer->width if (pool->width != buffer->width

View File

@ -18,8 +18,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <libgen.h>
#include <limits.h>
#include "cfg.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); 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) int cfg_load(struct cfg* self, const char* requested_path)
{ {
const char* path = requested_path ? requested_path const char* path = requested_path ? requested_path
: cfg__get_default_path(); : cfg__get_default_path();
if (!path) if (!path)
return -1; return -1;
@ -125,8 +118,6 @@ int cfg_load(struct cfg* self, const char* requested_path)
if (!stream) if (!stream)
return -1; return -1;
self->directory = cfg__dirname(path);
char* line = NULL; char* line = NULL;
size_t len = 0; size_t len = 0;
int lineno = 0; int lineno = 0;
@ -145,7 +136,6 @@ int cfg_load(struct cfg* self, const char* requested_path)
failure: failure:
cfg_destroy(self); cfg_destroy(self);
free(line); free(line);
free(self->directory);
fclose(stream); fclose(stream);
return lineno; return lineno;
} }
@ -163,5 +153,4 @@ void cfg_destroy(struct cfg* self)
#undef DESTROY_string #undef DESTROY_string
#undef DESTROY_uint #undef DESTROY_uint
#undef DESTROY_bool #undef DESTROY_bool
free(self->directory);
} }

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -26,22 +25,20 @@
#include <signal.h> #include <signal.h>
#include <assert.h> #include <assert.h>
#include <jansson.h> #include <jansson.h>
#include <sys/param.h>
#include "json-ipc.h" #include "json-ipc.h"
#include "ctl-client.h" #include "ctl-client.h"
#include "ctl-commands.h" #include "ctl-commands.h"
#include "ctl-server.h"
#include "strlcpy.h" #include "strlcpy.h"
#include "util.h" #include "util.h"
#include "option-parser.h"
#include "table-printer.h"
#define LOG(level, fmt, ...) \ #define LOG(level, fmt, ...) \
fprintf(stderr, level ": %s: %d: " fmt "\n", __FILE__, __LINE__, \ fprintf(stderr, "[%s:%d] <" level "> " fmt "\n", __FILE__, __LINE__, \
##__VA_ARGS__) ##__VA_ARGS__)
#define ERROR(fmt, ...) \ #define WARN(fmt, ...) \
LOG("ERROR", fmt, ##__VA_ARGS__) LOG("WARNING", fmt, ##__VA_ARGS__)
static bool do_debug = false; static bool do_debug = false;
@ -49,19 +46,8 @@ static bool do_debug = false;
if (do_debug) \ if (do_debug) \
LOG("DEBUG", fmt, ##__VA_ARGS__) LOG("DEBUG", fmt, ##__VA_ARGS__)
static struct cmd_info internal_events[] = { const char* EVT_LOCAL_SHUTDOWN = "wayvnc-shutdown";
{ .name = "wayvnc-startup", const char* EVT_LOCAL_STARTUP = "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 { struct ctl_client {
void* userdata; void* userdata;
@ -85,14 +71,13 @@ struct ctl_client* ctl_client_new(const char* socket_path, void* userdata)
{ {
if (!socket_path) if (!socket_path)
socket_path = default_ctl_socket_path(); socket_path = default_ctl_socket_path();
struct ctl_client* new = calloc(1, sizeof(*new)); struct ctl_client* new = calloc(1, sizeof(*new));
new->userdata = userdata; new->userdata = userdata;
new->fd = -1; new->fd = -1;
if (strlen(socket_path) >= sizeof(new->addr.sun_path)) { if (strlen(socket_path) >= sizeof(new->addr.sun_path)) {
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
ERROR("Failed to create unix socket: %m"); WARN("Failed to create unix socket: %m");
goto socket_failure; goto socket_failure;
} }
strcpy(new->addr.sun_path, socket_path); strcpy(new->addr.sun_path, socket_path);
@ -109,10 +94,9 @@ static int wait_for_socket(const char* socket_path, int timeout)
{ {
bool needs_log = true; bool needs_log = true;
struct stat sb; struct stat sb;
while (stat(socket_path, &sb) != 0) { while (stat(socket_path, &sb) != 0) {
if (timeout == 0) { if (timeout == 0) {
ERROR("Failed to find socket path \"%s\": %m", WARN("Failed to find socket path \"%s\": %m",
socket_path); socket_path);
return 1; return 1;
} }
@ -122,19 +106,17 @@ static int wait_for_socket(const char* socket_path, int timeout)
socket_path); socket_path);
} }
if (usleep(50000) == -1) { if (usleep(50000) == -1) {
ERROR("Failed to wait for socket path: %m"); WARN("Failed to wait for socket path: %m");
return -1; return -1;
} }
} }
if (S_ISSOCK(sb.st_mode)) { if (S_ISSOCK(sb.st_mode)) {
DEBUG("Found socket \"%s\"", socket_path); DEBUG("Found socket \"%s\"", socket_path);
} else { } else {
ERROR("Path \"%s\" exists but is not a socket (0x%x)", WARN("Path \"%s\" exists but is not a socket (0x%x)",
socket_path, sb.st_mode); socket_path, sb.st_mode);
return -1; return -1;
} }
return 0; return 0;
} }
@ -142,27 +124,23 @@ static int try_connect(struct ctl_client* self, int timeout)
{ {
if (self->fd != -1) if (self->fd != -1)
close(self->fd); close(self->fd);
self->fd = socket(AF_UNIX, SOCK_STREAM, 0); self->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (self->fd < 0) { if (self->fd < 0) {
ERROR("Failed to create unix socket: %m"); WARN("Failed to create unix socket: %m");
return 1; return 1;
} }
while (connect(self->fd, (struct sockaddr*)&self->addr, while (connect(self->fd, (struct sockaddr*)&self->addr,
sizeof(self->addr)) != 0) { sizeof(self->addr)) != 0) {
if (timeout == 0 || errno != ENOENT) { if (timeout == 0 || errno != ENOENT) {
ERROR("Failed to connect to unix socket \"%s\": %m", WARN("Failed to connect to unix socket \"%s\": %m",
self->addr.sun_path); self->addr.sun_path);
return 1; return 1;
} }
if (usleep(50000) == -1) { if (usleep(50000) == -1) {
ERROR("Failed to wait for connect to succeed: %m"); WARN("Failed to wait for connect to succeed: %m");
return 1; return 1;
} }
} }
return 0; return 0;
} }
@ -192,29 +170,42 @@ void* ctl_client_userdata(struct ctl_client* self)
} }
static struct jsonipc_request* ctl_client_parse_args(struct ctl_client* self, static struct jsonipc_request* ctl_client_parse_args(struct ctl_client* self,
enum cmd_type* cmd, struct option_parser* options) int argc, char* argv[])
{ {
struct jsonipc_request* request = NULL; struct jsonipc_request* request = NULL;
const char* method = argv[0];
json_t* params = json_object(); json_t* params = json_object();
struct cmd_info* info = ctl_command_by_type(*cmd); bool show_usage = false;
for (int i = 1; i < argc; ++i) {
if (option_parser_get_value(options, "help")) { char* key = argv[i];
json_object_set_new(params, "command", json_string(info->name)); char* value = NULL;
*cmd = CMD_HELP; if (strcmp(key, "--help") == 0 || strcmp(key, "-h") == 0) {
info = ctl_command_by_type(*cmd); show_usage = true;
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; continue;
}
if (key[0] == '-' && key[1] == '-')
key += 2;
char* delim = strchr(key, '=');
if (delim) {
*delim = '\0';
value = delim + 1;
} else if (++i < argc) {
value = argv[i];
} else {
WARN("Argument must be of the format --key=value or --key value");
goto failure;
}
json_object_set_new(params, key, json_string(value)); json_object_set_new(params, key, json_string(value));
} }
if (show_usage) {
// Special case for "foo --help"; convert into "help --command=foo"
json_object_clear(params);
json_object_set_new(params, "command", json_string(method));
method = "help";
}
request = jsonipc_request_new(method, params);
out: failure:
request = jsonipc_request_new(info->name, params);
json_decref(params); json_decref(params);
return request; return request;
} }
@ -223,25 +214,23 @@ static json_t* json_from_buffer(struct ctl_client* self)
{ {
if (self->read_len == 0) { if (self->read_len == 0) {
DEBUG("Read buffer is empty"); DEBUG("Read buffer is empty");
errno = EAGAIN; errno = ENODATA;
return NULL; return NULL;
} }
json_error_t err; json_error_t err;
json_t* root = json_loadb(self->read_buffer, self->read_len, JSON_DISABLE_EOF_CHECK, &err); json_t* root = json_loadb(self->read_buffer, self->read_len, 0, &err);
if (root) { if (root) {
advance_read_buffer(&self->read_buffer, &self->read_len, advance_read_buffer(&self->read_buffer, &self->read_len, err.position);
err.position);
} else if (json_error_code(&err) == json_error_premature_end_of_input) { } else if (json_error_code(&err) == json_error_premature_end_of_input) {
if (self->read_len == sizeof(self->read_buffer)) { if (self->read_len == sizeof(self->read_buffer)) {
ERROR("Response message is too long"); WARN("Response message is too long");
errno = EMSGSIZE; errno = EMSGSIZE;
} else { } else {
DEBUG("Awaiting more data"); DEBUG("Awaiting more data");
errno = EAGAIN; errno = ENODATA;
} }
} else { } else {
ERROR("Json parsing failed: %s", err.text); WARN("Json parsing failed: %s", err.text);
errno = EINVAL; errno = EINVAL;
} }
return root; return root;
@ -252,47 +241,40 @@ static json_t* read_one_object(struct ctl_client* self, int timeout_ms)
json_t* root = json_from_buffer(self); json_t* root = json_from_buffer(self);
if (root) if (root)
return root; return root;
if (errno != EAGAIN) if (errno != ENODATA)
return NULL; return NULL;
struct pollfd pfd = { struct pollfd pfd = {
.fd = self->fd, .fd = self->fd,
.events = POLLIN, .events = POLLIN,
.revents = 0 .revents = 0
}; };
while (root == NULL) {
while (!root) {
int n = poll(&pfd, 1, timeout_ms); int n = poll(&pfd, 1, timeout_ms);
if (n == -1) { if (n == -1) {
if (errno == EINTR && self->wait_for_events) if (errno == EINTR && self->wait_for_events)
continue; continue;
ERROR("Error waiting for a response: %m"); WARN("Error waiting for a response: %m");
break; break;
} else if (n == 0) { } else if (n == 0) {
ERROR("Timeout waiting for a response"); WARN("Timeout waiting for a response");
break; break;
} }
char* readptr = self->read_buffer + self->read_len; char* readptr = self->read_buffer + self->read_len;
size_t remainder = sizeof(self->read_buffer) - self->read_len; size_t remainder = sizeof(self->read_buffer) - self->read_len;
n = recv(self->fd, readptr, remainder, 0); n = recv(self->fd, readptr, remainder, 0);
if (n == -1) { if (n == -1) {
ERROR("Read failed: %m"); WARN("Read failed: %m");
break; break;
} else if (n == 0) { } else if (n == 0) {
ERROR("Disconnected"); WARN("Disconnected");
errno = ECONNRESET; errno = ECONNRESET;
break; break;
} }
DEBUG("Read %d bytes", n); DEBUG("Read %d bytes", n);
DEBUG("<< %.*s", n, readptr); DEBUG("<< %.*s", n, readptr);
self->read_len += n; self->read_len += n;
root = json_from_buffer(self); root = json_from_buffer(self);
if (!root && errno != EAGAIN) if (!root && errno != ENODATA)
break; break;
} }
return root; return root;
@ -304,17 +286,14 @@ static struct jsonipc_response* ctl_client_wait_for_response(struct ctl_client*
json_t* root = read_one_object(self, 1000); json_t* root = read_one_object(self, 1000);
if (!root) if (!root)
return NULL; return NULL;
struct jsonipc_error jipc_err = JSONIPC_ERR_INIT; struct jsonipc_error jipc_err = JSONIPC_ERR_INIT;
struct jsonipc_response* response = jsonipc_response_parse_new(root, struct jsonipc_response* response = jsonipc_response_parse_new(root,
&jipc_err); &jipc_err);
if (!response) { if (!response) {
char* msg = json_dumps(jipc_err.data, JSON_EMBED); char* msg = json_dumps(jipc_err.data, JSON_EMBED);
ERROR("Could not parse json: %s", msg); WARN("Could not parse json: %s", msg);
free(msg); free(msg);
} }
json_decref(root); json_decref(root);
jsonipc_error_cleanup(&jipc_err); jsonipc_error_cleanup(&jipc_err);
return response; return response;
@ -322,10 +301,9 @@ static struct jsonipc_response* ctl_client_wait_for_response(struct ctl_client*
static void print_error(struct jsonipc_response* response, const char* method) static void print_error(struct jsonipc_response* response, const char* method)
{ {
printf("ERROR: Failed to execute command: %s", method); printf("Error (%d)", response->code);
if (!response->data) if (!response->data)
goto out; goto out;
json_t* data = response->data; json_t* data = response->data;
if (json_is_string(data)) if (json_is_string(data))
printf(": %s", json_string_value(data)); printf(": %s", json_string_value(data));
@ -334,7 +312,6 @@ static void print_error(struct jsonipc_response* response, const char* method)
printf(": %s", json_string_value(json_object_get(data, "error"))); printf(": %s", json_string_value(json_object_get(data, "error")));
else else
json_dumpf(response->data, stdout, JSON_INDENT(2)); json_dumpf(response->data, stdout, JSON_INDENT(2));
out: out:
printf("\n"); printf("\n");
} }
@ -350,26 +327,29 @@ static void pretty_version(json_t* data)
static void pretty_client_list(json_t* data) static void pretty_client_list(json_t* data)
{ {
int n = json_array_size(data);
printf("There %s %d VNC client%s connected%s\n", (n == 1) ? "is" : "are",
n, (n == 1) ? "" : "s", (n > 0) ? ":" : ".");
size_t i; size_t i;
json_t* value; json_t* value;
json_array_foreach(data, i, value) { json_array_foreach(data, i, value) {
char* id = NULL; char* id = NULL;
char* address = NULL; char* hostname = NULL;
char* username = NULL; char* username = NULL;
json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "hostname",
json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "address", &hostname, "username", &username);
&address, "username", &username); printf(" client[%s]: ", id);
printf(" %s: ", id);
if (username) if (username)
printf("%s@", username); printf("%s@", username);
printf("%s\n", hostname ? hostname : "<unknown>");
printf("%s\n", address ? address : "<unknown>");
} }
} }
static void pretty_output_list(json_t* data) static void pretty_output_list(json_t* data)
{ {
int n = json_array_size(data);
printf("There %s %d output%s%s\n", (n == 1) ? "is" : "are",
n, (n == 1) ? "" : "s", (n > 0) ? ":" : ".");
size_t i; size_t i;
json_t* value; json_t* value;
json_array_foreach(data, i, value) { json_array_foreach(data, i, value) {
@ -378,18 +358,16 @@ static void pretty_output_list(json_t* data)
int height = -1; int height = -1;
int width = -1; int width = -1;
int captured = false; int captured = false;
json_unpack(value, "{s:s, s:s, s:i, s:i, s:b}", "name", &name, json_unpack(value, "{s:s, s:s, s:i, s:i, s:b}", "name", &name,
"description", &description, "description", &description,
"height", &height, "height", &height,
"width", &width, "width", &width,
"captured", &captured); "captured", &captured);
printf("%s %s: \"%s\" (%dx%d)\n", printf("%s output[%s]: %s (%dx%d)\n",
captured ? "*" : " ", name, description, width, captured ? "*" : " ", name, description, width,
height); height);
} }
} }
static void pretty_print(json_t* data, static void pretty_print(json_t* data,
struct jsonipc_request* request) struct jsonipc_request* request)
{ {
@ -398,17 +376,14 @@ static void pretty_print(json_t* data,
case CMD_VERSION: case CMD_VERSION:
pretty_version(data); pretty_version(data);
break; break;
case CMD_CLIENT_LIST: case CMD_GET_CLIENTS:
pretty_client_list(data); pretty_client_list(data);
break; break;
case CMD_OUTPUT_LIST: case CMD_GET_OUTPUTS:
pretty_output_list(data); pretty_output_list(data);
break; break;
case CMD_ATTACH: case CMD_DISCONNECT_CLIENT:
case CMD_DETACH: case CMD_SET_OUTPUT:
case CMD_CLIENT_DISCONNECT:
case CMD_OUTPUT_SET:
case CMD_OUTPUT_CYCLE:
case CMD_WAYVNC_EXIT: case CMD_WAYVNC_EXIT:
printf("Ok\n"); printf("Ok\n");
break; break;
@ -431,7 +406,6 @@ static int ctl_client_print_response(struct ctl_client* self,
struct jsonipc_response* response) struct jsonipc_response* response)
{ {
DEBUG("Response code: %d", response->code); DEBUG("Response code: %d", response->code);
if (response->data) { if (response->data) {
if (self->flags & CTL_CLIENT_PRINT_JSON) if (self->flags & CTL_CLIENT_PRINT_JSON)
print_compact_json(response->data); print_compact_json(response->data);
@ -440,12 +414,10 @@ static int ctl_client_print_response(struct ctl_client* self,
else else
print_error(response, request->method); print_error(response, request->method);
} }
return response->code; return response->code;
} }
static struct ctl_client* sig_target = NULL; static struct ctl_client* sig_target = NULL;
static void stop_loop(int signal) static void stop_loop(int signal)
{ {
sig_target->wait_for_events = false; sig_target->wait_for_events = false;
@ -470,11 +442,9 @@ static bool json_has_content(json_t* root)
{ {
if (!root) if (!root)
return false; return false;
size_t i; size_t i;
const char* key; const char* key;
json_t* value; json_t* value;
switch (json_typeof(root)) { switch (json_typeof(root)) {
case JSON_NULL: case JSON_NULL:
return false; return false;
@ -499,34 +469,39 @@ static bool json_has_content(json_t* root)
return false; return false;
} }
static void print_for_human(json_t* data, int level) static void print_as_yaml(json_t* data, int level, bool needs_leading_newline)
{ {
size_t i; size_t i;
const char* key; const char* key;
json_t* value; json_t* value;
bool needs_indent = needs_leading_newline;
switch(json_typeof(data)) { switch(json_typeof(data)) {
case JSON_NULL: case JSON_NULL:
printf("<null>\n"); printf("<null>\n");
break; break;
case JSON_OBJECT: case JSON_OBJECT:
if (json_object_size(data) > 0 && needs_leading_newline)
printf("\n");
json_object_foreach(data, key, value) { json_object_foreach(data, key, value) {
if (!json_has_content(value)) if (!json_has_content(value))
continue; continue;
if (needs_indent)
print_indent(level); print_indent(level);
else
needs_indent = true;
printf("%s: ", key); printf("%s: ", key);
print_for_human(value, level + 1); print_as_yaml(value, level + 1, true);
} }
break; break;
case JSON_ARRAY: case JSON_ARRAY:
if (json_array_size(data) > 0 && needs_leading_newline)
printf("\n");
json_array_foreach(data, i, value) { json_array_foreach(data, i, value) {
if (!json_has_content(value)) if (!json_has_content(value))
continue; continue;
print_indent(level); print_indent(level);
printf("- "); printf("- ");
print_for_human(value, level + 1); print_as_yaml(value, level + 1, json_is_array(value));
} }
break; break;
case JSON_STRING: case JSON_STRING:
@ -552,10 +527,11 @@ static void print_event(struct jsonipc_request* event, unsigned flags)
if (flags & CTL_CLIENT_PRINT_JSON) { if (flags & CTL_CLIENT_PRINT_JSON) {
print_compact_json(event->json); print_compact_json(event->json);
} else { } else {
printf("%s:\n", event->method); printf("\n%s:", event->method);
if (event->params) if (event->params)
print_for_human(event->params, 1); print_as_yaml(event->params, 1, true);
printf("\n"); else
printf("<<null>\n");
} }
fflush(stdout); fflush(stdout);
} }
@ -584,15 +560,13 @@ static ssize_t ctl_client_send_request(struct ctl_client* self,
json_error_t err; json_error_t err;
json_t* packed = jsonipc_request_pack(request, &err); json_t* packed = jsonipc_request_pack(request, &err);
if (!packed) { if (!packed) {
ERROR("Could not encode json: %s", err.text); WARN("Could not encode json: %s", err.text);
return -1; return -1;
} }
char buffer[512]; char buffer[512];
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT); int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
json_decref(packed); json_decref(packed);
DEBUG(">> %.*s", len, buffer); DEBUG(">> %.*s", len, buffer);
return send(self->fd, buffer, len, MSG_NOSIGNAL); return send(self->fd, buffer, len, MSG_NOSIGNAL);
} }
@ -616,31 +590,17 @@ static int ctl_client_register_for_events(struct ctl_client* self,
jsonipc_response_destroy(response); jsonipc_response_destroy(response);
if (result == 0) if (result == 0)
send_startup_event(self); send_startup_event(self);
return result; return result;
} }
static int ctl_client_reconnect_event_loop(struct ctl_client* self, static int ctl_client_reconnect_event_loop(struct ctl_client* self,
struct jsonipc_request* request) struct jsonipc_request* request, int timeout)
{ {
if (ctl_client_connect(self, -1) != 0) if (ctl_client_connect(self, timeout) != 0)
return -1; return -1;
return ctl_client_register_for_events(self, request); 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, static int ctl_client_event_loop(struct ctl_client* self,
struct jsonipc_request* request) struct jsonipc_request* request)
{ {
@ -657,8 +617,8 @@ static int ctl_client_event_loop(struct ctl_client* self,
if (errno == ECONNRESET) { if (errno == ECONNRESET) {
send_shutdown_event(self); send_shutdown_event(self);
if (self->flags & CTL_CLIENT_RECONNECT && if (self->flags & CTL_CLIENT_RECONNECT &&
block_until_reconnect( ctl_client_reconnect_event_loop(
self, request) == 0) self, request, -1) == 0)
continue; continue;
} }
break; break;
@ -669,22 +629,16 @@ static int ctl_client_event_loop(struct ctl_client* self,
print_event(event, self->flags); print_event(event, self->flags);
jsonipc_request_destroy(event); jsonipc_request_destroy(event);
} }
return 0; return 0;
} }
static int ctl_client_print_single_command(struct ctl_client* self, static int ctl_client_print_single_command(struct ctl_client* self,
enum cmd_type cmd, struct jsonipc_request* request) struct jsonipc_request* request)
{ {
struct jsonipc_response* response = ctl_client_run_single_command(self, struct jsonipc_response* response = ctl_client_run_single_command(self,
request); request);
if (!response) { if (!response)
if (errno == ECONNRESET && cmd == CMD_WAYVNC_EXIT)
return 0;
return 1; return 1;
}
int result = ctl_client_print_response(self, request, response); int result = ctl_client_print_response(self, request, response);
jsonipc_response_destroy(response); jsonipc_response_destroy(response);
return result; return result;
@ -693,236 +647,121 @@ static int ctl_client_print_single_command(struct ctl_client* self,
void ctl_client_print_command_list(FILE* stream) void ctl_client_print_command_list(FILE* stream)
{ {
fprintf(stream, "Commands:\n"); fprintf(stream, "Commands:\n");
size_t max_namelen = 0; for (size_t i = 0; i < CMD_LIST_LEN; ++i)
for (size_t i = 0; i < CMD_LIST_LEN; ++i) { fprintf(stream, " - %s\n", ctl_command_list[i].name);
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"); 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) void ctl_client_print_event_list(FILE* stream)
{ {
printf("Events:\n"); printf("Events:\n");
size_t max_namelen = 0;
for (size_t i = 0; i < EVT_LIST_LEN; ++i) for (size_t i = 0; i < EVT_LIST_LEN; ++i)
max_namelen = MAX(max_namelen, strlen(ctl_event_list[i].name)); printf(" - %s\n", ctl_event_list[i].name);
printf("\nRun 'wayvncctl help --event=event-name' for event-specific details.\n");
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, static void print_cmd_info_params(const struct cmd_info* info)
enum cmd_type cmd, {
struct option_parser* cmd_options, if (info->params[0].name != NULL) {
struct option_parser* parent_options) printf("\nParameters:");
for (int i = 0; info->params[i].name != NULL; ++i)
printf("\n --%s=...\n %s\n", info->params[i].name,
info->params[i].description);
}
}
static int print_command_usage(const char* cmd_name)
{
enum cmd_type type = ctl_command_parse_name(cmd_name);
struct cmd_info* info = ctl_command_by_type(type);
if (!info) {
WARN("No such command \"%s\"\n", cmd_name);
return 1;
}
bool params = info->params[0].name != NULL;
printf("Usage: wayvncctl [options] %s%s\n\n%s\n", info->name,
params ? " [params]" : "",
info->description);
print_cmd_info_params(info);
printf("\nRun 'wayvncctl --help' for allowed options\n");
if (type == CMD_EVENT_RECEIVE) {
printf("\n");
ctl_client_print_event_list(stdout);
}
return 0;
}
static int print_event_details(const char* evt_name)
{
struct cmd_info* info = ctl_event_by_name(evt_name);
if (!info) {
WARN("No such event \"%s\"\n", evt_name);
return 1;
}
printf("Event: %s\n\n%s\n", info->name,
info->description);
print_cmd_info_params(info);
return 0;
}
static int ctl_client_print_help(struct ctl_client* self,
struct jsonipc_request* request)
{ {
if (self->flags & CTL_CLIENT_PRINT_JSON) { if (self->flags & CTL_CLIENT_PRINT_JSON) {
ERROR("JSON output is not supported for \"help\" output"); WARN("JSON output is not supported for the \"help\" command");
return 1; return 1;
} }
struct cmd_info* info = ctl_command_by_type(cmd); json_t* params = request->params;
if (!info) { const char* cmd_name = NULL;
ERROR("No such command"); const char* evt_name = NULL;
return 1; if (params)
} json_unpack(params, "{s?s, s?s}", "command", &cmd_name,
"event", &evt_name);
printf("Usage: wayvncctl [options] %s", info->name); if (cmd_name)
option_parser_print_usage(cmd_options, stdout); return print_command_usage(cmd_name);
printf("\n"); if (evt_name)
option_parser_print_cmd_summary(info->description, stdout); return print_event_details(evt_name);
if (option_parser_print_arguments(cmd_options, stdout))
printf("\n");
option_parser_print_options(cmd_options, stdout); ctl_client_print_command_list(stdout);
printf("\n"); printf("\n");
option_parser_print_options(parent_options, stdout);
printf("\n");
if (cmd == CMD_EVENT_RECEIVE) {
ctl_client_print_event_list(stdout); ctl_client_print_event_list(stdout);
printf("\n");
}
return 0; 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, int ctl_client_run_command(struct ctl_client* self,
struct option_parser* parent_options, unsigned flags) int argc, char* argv[], unsigned flags)
{ {
self->flags = flags; self->flags = flags;
int result = 1; int result = 1;
struct jsonipc_request* request = ctl_client_parse_args(self, argc,
const char* method = option_parser_get_value(parent_options, "command"); argv);
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) if (!request)
goto parse_failure; goto parse_failure;
enum cmd_type cmd = ctl_command_parse_name(request->method);
if (cmd != CMD_HELP) {
int timeout = (flags & CTL_CLIENT_SOCKET_WAIT) ? -1 : 0; int timeout = (flags & CTL_CLIENT_SOCKET_WAIT) ? -1 : 0;
result = ctl_client_connect(self, timeout); result = ctl_client_connect(self, timeout);
if (result != 0) if (result != 0)
goto connect_failure; return result;
}
switch (cmd) { switch (cmd) {
case CMD_HELP:
result = ctl_client_print_help(self, request);
break;
case CMD_EVENT_RECEIVE: case CMD_EVENT_RECEIVE:
result = ctl_client_event_loop(self, request); result = ctl_client_event_loop(self, request);
break; break;
default: default:
result = ctl_client_print_single_command(self, cmd, request); result = ctl_client_print_single_command(self, request);
break; break;
} }
connect_failure:
jsonipc_request_destroy(request); jsonipc_request_destroy(request);
help_printed:
parse_failure: parse_failure:
ctl_client_destroy_cmd_parser(&cmd_options);
return result; return result;
} }

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -21,127 +20,76 @@
#include <string.h> #include <string.h>
struct cmd_info ctl_command_list[] = { 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", [CMD_HELP] = { "help",
"List all commands and events, or show usage of a specific command or event", "List all commands and events, or show usage of a specific command or event",
{ {
{ "command", {"command", "The command to show (optional)"},
"The command to show (optional)", {"event", "The event to show (optional)"},
"<name>" }, {NULL, NULL},
{ "event",
"The event to show (optional)",
"<name>" },
{},
} }
}, },
[CMD_VERSION] = { "version", [CMD_VERSION] = { "version",
"Query the version of the wayvnc process", "Query the version of the wayvnc process",
{{}} {{NULL, NULL}}
}, },
[CMD_EVENT_RECEIVE] = { "event-receive", [CMD_EVENT_RECEIVE] = { "event-receive",
"Register to begin receiving asynchronous events from wayvnc", "Register to begin receiving asynchronous events from wayvnc",
// TODO: Event type filtering? // TODO: Event type filtering?
{{}} {{NULL, NULL}}
}, },
[CMD_CLIENT_LIST] = { "client-list", [CMD_SET_OUTPUT] = { "set-output",
"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", "Switch the actively captured output",
{ {
{ "output-name", {"switch-to", "The specific output name to capture"},
"The specific output name to capture", {"cycle", "Either \"next\" or \"prev\""},
"<string>", true }, {NULL, NULL},
{}, }
},
[CMD_GET_CLIENTS] = { "get-clients",
"Return a list of all currently connected VNC sessions",
{{NULL, NULL}}
},
[CMD_GET_OUTPUTS] = { "get-outputs",
"Return a list of all currently detected Wayland outputs",
{{NULL, NULL}}
},
[CMD_DISCONNECT_CLIENT] = { "disconnect-client",
"Disconnect a VNC session",
{
{"id", "The ID of the client to disconnect"},
{NULL, NULL},
} }
}, },
[CMD_WAYVNC_EXIT] = { "wayvnc-exit", [CMD_WAYVNC_EXIT] = { "wayvnc-exit",
"Disconnect all clients and shut down wayvnc", "Disconnect all clients and shut down wayvnc",
{{}}, {{NULL,NULL}},
}, },
}; };
#define CLIENT_EVENT_PARAMS(including) \ #define CLIENT_EVENT_PARAMS(including) \
{ "id", \ {"id", "A unique identifier for this client"}, \
"A unique identifier for this client", \ {"connection_count", "The total number of connected VNC clients " including " this one."}, \
"<integer>" }, \ {"hostname", "The hostname or IP address of this client (may be null)"}, \
{ "connection_count", \ {"username", "The username used to authentice this client (may be null)."}, \
"The total number of connected VNC clients " including " this one.", \ {NULL, NULL},
"<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[] = { 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", [EVT_CLIENT_CONNECTED] = {"client-connected",
"Sent by wayvnc when a new VNC client connects", "Sent when a new vnc client connects to wayvnc",
{ CLIENT_EVENT_PARAMS("including") } { CLIENT_EVENT_PARAMS("including") }
}, },
[EVT_CLIENT_DISCONNECTED] = {"client-disconnected", [EVT_CLIENT_DISCONNECTED] = {"client-disconnected",
"Sent by waynvc when a VNC client disconnects", "Sent when a vnc client disconnects from wayvnc",
{ CLIENT_EVENT_PARAMS("not including") } { CLIENT_EVENT_PARAMS("not including") }
}, },
[EVT_DETACHED] = {"detached", [EVT_CAPTURE_CHANGED] = {"capture-changed",
"Sent after detaching from compositor", "Sent when wayvnc changes which output is captured",
{}
},
[EVT_OUTPUT_ADDED] = {"output-added",
"Sent when an output is added by the compositor",
{ {
{ "name", "Output name", "<string>" }, {"output", "The name of the output now being captured"},
{} {NULL, NULL},
}
}, },
[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) enum cmd_type ctl_command_parse_name(const char* name)

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -27,8 +26,6 @@
#include <neatvnc.h> #include <neatvnc.h>
#include <aml.h> #include <aml.h>
#include <jansson.h> #include <jansson.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "output.h" #include "output.h"
#include "ctl-commands.h" #include "ctl-commands.h"
@ -49,11 +46,6 @@ struct cmd {
enum cmd_type type; enum cmd_type type;
}; };
struct cmd_attach {
struct cmd cmd;
char display[128];
};
struct cmd_help { struct cmd_help {
struct cmd cmd; struct cmd cmd;
char id[64]; char id[64];
@ -113,19 +105,6 @@ static void cmd_response_destroy(struct cmd_response* self)
free(self); free(self);
} }
static struct cmd_attach* cmd_attach_new(json_t* args,
struct jsonipc_error* err)
{
const char* display = NULL;
if (json_unpack(args, "{s:s}", "display", &display) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing display name");
return NULL;
}
struct cmd_attach* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->display, display, sizeof(cmd->display));
return cmd;
}
static struct cmd_help* cmd_help_new(json_t* args, static struct cmd_help* cmd_help_new(json_t* args,
struct jsonipc_error* err) struct jsonipc_error* err)
{ {
@ -158,12 +137,34 @@ static struct cmd_set_output* cmd_set_output_new(json_t* args,
struct jsonipc_error* err) struct jsonipc_error* err)
{ {
const char* target = NULL; const char* target = NULL;
if (json_unpack(args, "{s:s}", "output-name", &target) == -1) { const char* cycle = NULL;
jsonipc_error_printf(err, EINVAL, "Missing output name"); if (json_unpack(args, "{s?s,s?s}",
"switch-to", &target,
"cycle", &cycle) == -1) {
jsonipc_error_printf(err, EINVAL,
"expecting \"switch-to\" or \"cycle\"");
return NULL;
}
if ((!target && !cycle) || (target && cycle)) {
jsonipc_error_printf(err, EINVAL,
"expecting exactly one of \"switch-to\" or \"cycle\"");
return NULL; return NULL;
} }
struct cmd_set_output* cmd = calloc(1, sizeof(*cmd)); struct cmd_set_output* cmd = calloc(1, sizeof(*cmd));
if (target) {
strlcpy(cmd->target, target, sizeof(cmd->target)); strlcpy(cmd->target, target, sizeof(cmd->target));
} else if (cycle) {
if (strncmp(cycle, "prev", 4) == 0)
cmd->cycle = OUTPUT_CYCLE_REVERSE;
else if (strcmp(cycle, "next") == 0)
cmd->cycle = OUTPUT_CYCLE_FORWARD;
else {
jsonipc_error_printf(err, EINVAL,
"cycle must either be \"next\" or \"prev\"");
free(cmd);
return NULL;
}
}
return cmd; return cmd;
} }
@ -171,8 +172,10 @@ static struct cmd_disconnect_client* cmd_disconnect_client_new(json_t* args,
struct jsonipc_error* err) struct jsonipc_error* err)
{ {
const char* id = NULL; const char* id = NULL;
if (json_unpack(args, "{s:s}", "id", &id) == -1) { if (json_unpack(args, "{s:s}",
jsonipc_error_printf(err, EINVAL, "Missing client id"); "id", &id) == -1) {
jsonipc_error_printf(err, EINVAL,
"required: \"id\"");
return NULL; return NULL;
} }
struct cmd_disconnect_client* cmd = calloc(1, sizeof(*cmd)); struct cmd_disconnect_client* cmd = calloc(1, sizeof(*cmd));
@ -206,24 +209,19 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
enum cmd_type cmd_type = ctl_command_parse_name(ipc->method); enum cmd_type cmd_type = ctl_command_parse_name(ipc->method);
struct cmd* cmd = NULL; struct cmd* cmd = NULL;
switch (cmd_type) { switch (cmd_type) {
case CMD_ATTACH:
cmd = (struct cmd*)cmd_attach_new(ipc->params, err);
break;
case CMD_HELP: case CMD_HELP:
cmd = (struct cmd*)cmd_help_new(ipc->params, err); cmd = (struct cmd*)cmd_help_new(ipc->params, err);
break; break;
case CMD_OUTPUT_SET: case CMD_SET_OUTPUT:
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err); cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
break; break;
case CMD_CLIENT_DISCONNECT: case CMD_DISCONNECT_CLIENT:
cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err); cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err);
break; break;
case CMD_DETACH:
case CMD_VERSION: case CMD_VERSION:
case CMD_EVENT_RECEIVE: case CMD_EVENT_RECEIVE:
case CMD_CLIENT_LIST: case CMD_GET_CLIENTS:
case CMD_OUTPUT_LIST: case CMD_GET_OUTPUTS:
case CMD_OUTPUT_CYCLE:
case CMD_WAYVNC_EXIT: case CMD_WAYVNC_EXIT:
cmd = calloc(1, sizeof(*cmd)); cmd = calloc(1, sizeof(*cmd));
break; break;
@ -234,9 +232,8 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
jprintf("Unknown command \"%s\"", jprintf("Unknown command \"%s\"",
ipc->method), ipc->method),
"commands", list_allowed_commands())); "commands", list_allowed_commands()));
break; return NULL;
} }
if (cmd)
cmd->type = cmd_type; cmd->type = cmd_type;
return cmd; return cmd;
} }
@ -353,76 +350,24 @@ static struct cmd_response* generate_version_object()
return response; return response;
} }
static struct ctl_server_client* ctl_server_client_first(struct ctl* self)
{
return self->actions.client_next(self, NULL);
}
static struct ctl_server_client* ctl_server_client_next(struct ctl* self,
struct ctl_server_client* prev)
{
return self->actions.client_next(self, prev);
}
static int sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr)
{
struct sockaddr_in* sa_in = (struct sockaddr_in*)addr;
struct sockaddr_in6* sa_in6 = (struct sockaddr_in6*)addr;
switch (addr->sa_family) {
case AF_INET:
inet_ntop(addr->sa_family, &sa_in->sin_addr, dst, sz);
return 0;
case AF_INET6:
inet_ntop(addr->sa_family, &sa_in6->sin6_addr, dst, sz);
return 0;
}
nvnc_log(NVNC_LOG_DEBUG,
"Don't know how to convert sa_family %d to string",
addr->sa_family);
return -1;
}
static void ctl_server_client_get_info(struct ctl* self,
const struct ctl_server_client* client,
struct ctl_server_client_info* info)
{
return self->actions.client_info(client, info);
}
static struct cmd_response* generate_vnc_client_list(struct ctl* self) static struct cmd_response* generate_vnc_client_list(struct ctl* self)
{ {
struct ctl_server_vnc_client* clients;
size_t num_clients = self->actions.get_client_list(self, &clients);
struct cmd_response* response = cmd_ok(); struct cmd_response* response = cmd_ok();
response->data = json_array(); response->data = json_array();
for (size_t i = 0; i < num_clients; ++i) {
struct ctl_server_client* client; json_t* packed = json_pack("{s:s}", "id", clients[i].id);
for (client = ctl_server_client_first(self); client; if (clients[i].hostname[0] != '\0')
client = ctl_server_client_next(self, client)) { json_object_set_new(packed, "hostname",
struct ctl_server_client_info info = {}; json_string(clients[i].hostname));
ctl_server_client_get_info(self, client, &info); if (clients[i].username[0] != '\0')
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info.id);
json_t* packed = json_pack("{s:s}", "id", id_str);
char address_string[256];
if (sockaddr_to_string(address_string, sizeof(address_string),
&info.address) == 0) {
json_object_set_new(packed, "address",
json_string(address_string));
}
if (info.username)
json_object_set_new(packed, "username", json_object_set_new(packed, "username",
json_string(info.username)); json_string(clients[i].username));
if (info.seat)
json_object_set_new(packed, "seat",
json_string(info.seat));
json_array_append_new(response->data, packed); json_array_append_new(response->data, packed);
} }
free(clients);
return response; return response;
} }
@ -454,30 +399,25 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name); nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name);
struct cmd_response* response = NULL; struct cmd_response* response = NULL;
switch (cmd->type) { switch (cmd->type) {
case CMD_ATTACH:{
struct cmd_attach* c = (struct cmd_attach*)cmd;
response = self->actions.on_attach(self, c->display);
break;
}
case CMD_HELP:{ case CMD_HELP:{
struct cmd_help* c = (struct cmd_help*)cmd; struct cmd_help* c = (struct cmd_help*)cmd;
response = generate_help_object(c->id, c->id_is_command); response = generate_help_object(c->id, c->id_is_command);
break; break;
} }
case CMD_OUTPUT_SET: { case CMD_SET_OUTPUT: {
struct cmd_set_output* c = (struct cmd_set_output*)cmd; struct cmd_set_output* c = (struct cmd_set_output*)cmd;
if (c->target[0] != '\0')
response = self->actions.on_output_switch(self, c->target); response = self->actions.on_output_switch(self, c->target);
else
response = self->actions.on_output_cycle(self, c->cycle);
break; break;
} }
case CMD_CLIENT_DISCONNECT: { case CMD_DISCONNECT_CLIENT: {
struct cmd_disconnect_client* c = struct cmd_disconnect_client* c =
(struct cmd_disconnect_client*)cmd; (struct cmd_disconnect_client*)cmd;
response = self->actions.on_disconnect_client(self, c->id); response = self->actions.on_disconnect_client(self, c->id);
break; break;
} }
case CMD_DETACH:
response = self->actions.on_detach(self);
break;
case CMD_WAYVNC_EXIT: case CMD_WAYVNC_EXIT:
response = self->actions.on_wayvnc_exit(self); response = self->actions.on_wayvnc_exit(self);
break; break;
@ -488,15 +428,12 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
client->accept_events = true; client->accept_events = true;
response = cmd_ok(); response = cmd_ok();
break; break;
case CMD_CLIENT_LIST: case CMD_GET_CLIENTS:
response = generate_vnc_client_list(self); response = generate_vnc_client_list(self);
break; break;
case CMD_OUTPUT_LIST: case CMD_GET_OUTPUTS:
response = generate_output_list(self); response = generate_output_list(self);
break; break;
case CMD_OUTPUT_CYCLE:
response = self->actions.on_output_cycle(self, OUTPUT_CYCLE_FORWARD);
break;
case CMD_UNKNOWN: case CMD_UNKNOWN:
break; break;
} }
@ -914,22 +851,15 @@ struct cmd_response* cmd_failed(const char* fmt, ...)
} }
json_t* pack_connection_event_params( json_t* pack_connection_event_params(
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count) int new_connection_count)
{ {
// TODO: Why is the id a string? return json_pack("{s:s, s:s?, s:s?, s:i}",
char id_str[64]; "id", client_id,
snprintf(id_str, sizeof(id_str), "%d", info->id); "hostname", client_hostname,
"username", client_username,
char address_string[256];
bool have_addr = sockaddr_to_string(address_string,
sizeof(address_string), &info->address) == 0;
return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}",
"id", id_str,
"address", have_addr ? address_string : NULL,
"username", info->username,
"seat", info->seat,
"connection_count", new_connection_count); "connection_count", new_connection_count);
} }
@ -971,28 +901,34 @@ int ctl_server_enqueue_event(struct ctl* self, enum event_type evt_type,
static void ctl_server_event_connect(struct ctl* self, static void ctl_server_event_connect(struct ctl* self,
enum event_type evt_type, enum event_type evt_type,
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count) int new_connection_count)
{ {
json_t* params = json_t* params = pack_connection_event_params(client_id, client_hostname,
pack_connection_event_params(info, new_connection_count); client_username, new_connection_count);
ctl_server_enqueue_event(self, evt_type, params); ctl_server_enqueue_event(self, evt_type, params);
} }
void ctl_server_event_connected(struct ctl* self, void ctl_server_event_connected(struct ctl* self,
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count) int new_connection_count)
{ {
ctl_server_event_connect(self, EVT_CLIENT_CONNECTED, info, ctl_server_event_connect(self, EVT_CLIENT_CONNECTED, client_id,
new_connection_count); client_hostname, client_username, new_connection_count);
} }
void ctl_server_event_disconnected(struct ctl* self, void ctl_server_event_disconnected(struct ctl* self,
const struct ctl_server_client_info *info, const char* client_id,
const char* client_hostname,
const char* client_username,
int new_connection_count) int new_connection_count)
{ {
ctl_server_event_connect(self, EVT_CLIENT_DISCONNECTED, info, ctl_server_event_connect(self, EVT_CLIENT_DISCONNECTED, client_id,
new_connection_count); client_hostname, client_username, new_connection_count);
} }
void ctl_server_event_capture_changed(struct ctl* self, void ctl_server_event_capture_changed(struct ctl* self,
@ -1001,20 +937,3 @@ void ctl_server_event_capture_changed(struct ctl* self,
ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED, ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED,
json_pack("{s:s}", "output", captured_output)); json_pack("{s:s}", "output", captured_output));
} }
void ctl_server_event_detached(struct ctl* self)
{
ctl_server_enqueue_event(self, EVT_DETACHED, json_object());
}
void ctl_server_event_output_added(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_ADDED,
json_pack("{s:s}", "name", name));
}
void ctl_server_event_output_removed(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_REMOVED,
json_pack("{s:s}", "name", name));
}

View File

@ -436,18 +436,3 @@ void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code,
send_key(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;
}

File diff suppressed because it is too large Load Diff

View File

@ -16,14 +16,11 @@
#include "option-parser.h" #include "option-parser.h"
#include "strlcpy.h" #include "strlcpy.h"
#include "table-printer.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <assert.h> #include <assert.h>
#include <sys/param.h>
#include <ctype.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
@ -42,7 +39,6 @@ void option_parser_init(struct option_parser* self,
self->options = options; self->options = options;
self->n_opts = count_options(options); self->n_opts = count_options(options);
self->name = "Options";
} }
static int get_left_col_width(const struct wv_option* opts, int n) static int get_left_col_width(const struct wv_option* opts, int n)
@ -75,103 +71,82 @@ static int get_left_col_width(const struct wv_option* opts, int n)
return max_width; return max_width;
} }
static const char* format_help(const struct wv_option* opt) static void reflow_text(char* dst, const char* src, int width)
{ {
if (!opt->default_) int line_len = 0;
return opt->help; int last_space_pos = 0;
static char help_buf[256]; int dst_len = 0;
snprintf(help_buf, sizeof(help_buf), "%s\nDefault: %s", opt->help, opt->default_); int i = 0;
return help_buf;
while (src[i]) {
char c = src[i];
if (line_len > width) {
assert(last_space_pos > 0);
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
} }
static void format_option(struct table_printer* printer, const struct wv_option* opt) if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
++i;
++line_len;
}
dst[dst_len] = '\0';
}
static void format_option(const struct wv_option* opt, int left_col_width,
FILE* stream)
{ {
if (!opt->help || opt->positional) if (!opt->help)
return; return;
int n_chars = 0; int n_chars = fprintf(stream, " ");
char buf[64];
if (opt->short_opt) if (opt->short_opt)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, n_chars += fprintf(stream, "-%c", opt->short_opt);
"-%c", opt->short_opt);
if (opt->long_opt) if (opt->long_opt)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, n_chars += fprintf(stream, "%s--%s",
"%s--%s", opt->short_opt ? "," : "", opt->short_opt ? "," : "", opt->long_opt);
opt->long_opt);
if (opt->schema) if (opt->schema)
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars, n_chars += fprintf(stream, "%s%s",
"%s%s", opt->long_opt ? "=" : "", opt->schema); opt->long_opt ? "=" : "", opt->schema);
table_printer_print_line(printer, buf, format_help(opt)); n_chars += fprintf(stream, "%*s", left_col_width - n_chars + 8, "");
int right_col_width = 80 - 8 - left_col_width;
assert(right_col_width >= 0);
char help[256];
reflow_text(help, opt->help, right_col_width);
char* line = strtok(help, "\n");
fprintf(stream, "%s\n", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", left_col_width + 8, "", line);
}
} }
void option_parser_print_options(struct option_parser* self, FILE* stream) void option_parser_print_options(struct option_parser* self, FILE* stream)
{ {
fprintf(stream, "%s:\n", self->name); fprintf(stream, "Options:\n");
int left_col_width = get_left_col_width(self->options, self->n_opts); 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) { for (int i = 0; i < self->n_opts; ++i) {
const struct wv_option* opt = &self->options[i]; format_option(&self->options[i], left_col_width, stream);
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( static const struct wv_option* find_long_option(
@ -212,19 +187,6 @@ static const struct wv_option* find_positional_option(
return NULL; 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, static int append_value(struct option_parser* self,
const struct wv_option* option, const char* value) const struct wv_option* option, const char* value)
{ {
@ -344,10 +306,8 @@ int option_parser_parse(struct option_parser* self, int argc,
while (i < argc) { while (i < argc) {
if (argv[i][0] == '-') { if (argv[i][0] == '-') {
if (argv[i][1] == '-') { if (argv[i][1] == '-') {
if (argv[i][2] == '\0') { if (argv[i][2] == '\0')
i++; return 0;
break;
}
int rc = parse_long_arg(self, argc, argv, i); int rc = parse_long_arg(self, argc, argv, i);
if (rc < 0) if (rc < 0)
@ -368,13 +328,11 @@ int option_parser_parse(struct option_parser* self, int argc,
i += rc; i += rc;
} }
} }
self->remaining_argc = argc - i; self->endpos = i;
if (self->remaining_argc)
self->remaining_argv = argv + i;
return 0; return 0;
} }
const char* option_parser_get_value_no_default(const struct option_parser* self, const char* option_parser_get_value(const struct option_parser* self,
const char* name) const char* name)
{ {
const struct wv_option* opt; const struct wv_option* opt;
@ -397,18 +355,6 @@ const char* option_parser_get_value_no_default(const struct option_parser* self,
return value->value; 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) { if (is_short) {
opt = find_short_option(self, name[0]); opt = find_short_option(self, name[0]);
if (opt) if (opt)
@ -417,19 +363,9 @@ const char* option_parser_get_value(const struct option_parser* self,
opt = find_long_option(self, name); opt = find_long_option(self, name);
if (opt) if (opt)
return opt->default_; return opt->default_;
opt = find_positional_option_by_name(self, name);
if (opt)
return opt->default_;
} }
// TODO: Add positional option?
return NULL; 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

@ -219,12 +219,7 @@ void output_name(void* data, struct zxdg_output_v1* xdg_output,
struct output* self = data; struct output* self = data;
strlcpy(self->name, name, sizeof(self->name)); strlcpy(self->name, name, sizeof(self->name));
self->is_headless = nvnc_trace("Output %u name: %s", self->id, self->name);
(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, void output_description(void* data, struct zxdg_output_v1* xdg_output,

View File

@ -30,7 +30,7 @@ static int pam_return_pwd(int num_msg, const struct pam_message** msgm,
struct pam_response** response, void* appdata_ptr) struct pam_response** response, void* appdata_ptr)
{ {
struct credentials* cred = appdata_ptr; struct credentials* cred = appdata_ptr;
struct pam_response* resp = calloc(num_msg, sizeof(*resp)); struct pam_response* resp = calloc(sizeof(*response), num_msg);
for (int i = 0; i < num_msg; i++) { for (int i = 0; i < num_msg; i++) {
resp[i].resp_retcode = PAM_SUCCESS; resp[i].resp_retcode = PAM_SUCCESS;
switch(msgm[i]->msg_style) { switch(msgm[i]->msg_style) {

View File

@ -44,40 +44,3 @@ uint32_t fourcc_from_wl_shm(enum wl_shm_format in)
return in; 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

@ -298,8 +298,5 @@ void screencopy_destroy(struct screencopy* self)
if (self->front) if (self->front)
wv_buffer_pool_release(self->pool, self->front); wv_buffer_pool_release(self->pool, self->front);
self->back = NULL;
self->front = NULL;
wv_buffer_pool_destroy(self->pool); wv_buffer_pool_destroy(self->pool);
} }

View File

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

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

@ -23,7 +23,9 @@
#include "util.h" #include "util.h"
const char* wayvnc_version = const char* wayvnc_version =
#if defined(PROJECT_VERSION) #if defined(GIT_VERSION)
GIT_VERSION;
#elif defined(PROJECT_VERSION)
PROJECT_VERSION; PROJECT_VERSION;
#else #else
"UNKNOWN"; "UNKNOWN";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -43,11 +43,11 @@ struct wayvncctl {
static int wayvncctl_usage(FILE* stream, struct option_parser* options, int rc) static int wayvncctl_usage(FILE* stream, struct option_parser* options, int rc)
{ {
fprintf(stream, "Usage: wayvncctl"); static const char* usage =
option_parser_print_usage(options, stream); "Usage: wayvncctl [options] [command [--param1=value1 ...]]\n"
fprintf(stream, " [parameters]\n"); "\n"
option_parser_print_cmd_summary( "Connects to and interacts with a running wayvnc instance.";
"Connects to and interacts with a running wayvnc instance.", stream); fprintf(stream, "%s\n\n", usage);
option_parser_print_options(options, stream); option_parser_print_options(options, stream);
fprintf(stream, "\n"); fprintf(stream, "\n");
ctl_client_print_command_list(stream); ctl_client_print_command_list(stream);
@ -112,7 +112,10 @@ int main(int argc, char* argv[])
// No command; nothing to do... // No command; nothing to do...
if (!option_parser_get_value(&option_parser, "command")) if (!option_parser_get_value(&option_parser, "command"))
return wayvncctl_usage(stdout, &option_parser, 1); return 0;
argc -= option_parser.endpos;
argv += option_parser.endpos;
ctl_client_debug_log(verbose); ctl_client_debug_log(verbose);
@ -120,7 +123,7 @@ int main(int argc, char* argv[])
if (!self.ctl) if (!self.ctl)
goto ctl_client_failure; goto ctl_client_failure;
int result = ctl_client_run_command(self.ctl, &option_parser, flags); int result = ctl_client_run_command(self.ctl, argc, argv, flags);
ctl_client_destroy(self.ctl); ctl_client_destroy(self.ctl);

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

@ -15,13 +15,6 @@ static const struct wv_option options[] = {
{ }, { },
}; };
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) static int test_simple(void)
{ {
struct option_parser parser; struct option_parser parser;
@ -43,40 +36,10 @@ static int test_simple(void)
ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_TRUE(option_parser_get_value(&parser, "option-b")); ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
ASSERT_FALSE(option_parser_get_value(&parser, "value-option")); ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
ASSERT_INT_EQ(0, parser.remaining_argc);
ASSERT_FALSE(parser.remaining_argv);
return 0; 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) static int test_short_value_option_with_space(void)
{ {
struct option_parser parser; struct option_parser parser;
@ -166,8 +129,6 @@ static int test_stop(void)
ASSERT_TRUE(option_parser_get_value(&parser, "a")); ASSERT_TRUE(option_parser_get_value(&parser, "a"));
ASSERT_FALSE(option_parser_get_value(&parser, "b")); 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; return 0;
} }
@ -214,9 +175,8 @@ static int test_subcommand_without_arguments(void)
const char* argv[] = { "executable", "-ab", "first", "second", "third", const char* argv[] = { "executable", "-ab", "first", "second", "third",
"do-stuff" }; "do-stuff" };
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_INT_EQ(5, parser.endpos);
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command")); 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; return 0;
} }
@ -227,65 +187,8 @@ static int test_subcommand_with_arguments(void)
const char* argv[] = { "executable", "-ab", "first", "second", "third", const char* argv[] = { "executable", "-ab", "first", "second", "third",
"do-stuff", "--some-option", "another-argument"}; "do-stuff", "--some-option", "another-argument"};
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv)); ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
ASSERT_INT_EQ(5, parser.endpos);
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command")); 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; return 0;
} }
@ -293,7 +196,6 @@ int main()
{ {
int r = 0; int r = 0;
RUN_TEST(test_simple); RUN_TEST(test_simple);
RUN_TEST(test_extra_positional_args);
RUN_TEST(test_short_value_option_with_space); RUN_TEST(test_short_value_option_with_space);
RUN_TEST(test_short_value_option_without_space); RUN_TEST(test_short_value_option_without_space);
RUN_TEST(test_short_value_option_with_eq); RUN_TEST(test_short_value_option_with_eq);
@ -308,7 +210,5 @@ int main()
RUN_TEST(test_missing_long_value); RUN_TEST(test_missing_long_value);
RUN_TEST(test_subcommand_without_arguments); RUN_TEST(test_subcommand_without_arguments);
RUN_TEST(test_subcommand_with_arguments); RUN_TEST(test_subcommand_with_arguments);
RUN_TEST(test_defaults_not_set);
RUN_TEST(test_defaults_overridden);
return r; 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,8 +1,6 @@
#!/bin/bash #!/bin/sh
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
valgrind --leak-check=full \ valgrind --leak-check=full \
--show-leak-kinds=all \ --show-leak-kinds=all \
--suppressions=$SCRIPT_DIR/valgrind.supp \ --suppressions=util/valgrind.supp \
$@ $@

View File

@ -52,9 +52,6 @@ wayvnc - A VNC server for wlroots based Wayland compositors.
*-v,--verbose* *-v,--verbose*
Be more verbose. Same as setting `--log-level=info`. Be more verbose. Same as setting `--log-level=info`.
*-w,--websocket*
Create a websocket.
*-L,--log-level* *-L,--log-level*
Set log level. The levels are: error, warning, info, debug, trace and Set log level. The levels are: error, warning, info, debug, trace and
quiet. quiet.
@ -75,11 +72,11 @@ 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 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_. 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 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* startup, or you can query them at runtime via the *wayvncctl get-outputs*
command. command.
You can also change which output is being captured on the fly via the *wayvncctl You can also change which output is being captured on the fly via the *wayvncctl
output-set* command. set-output* command.
# CONFIGURATION # CONFIGURATION
@ -114,24 +111,13 @@ considered to be part of the key or the value.
*port* *port*
The port to which the server shall bind. Default is 5900. The port to which the server shall bind. Default is 5900.
*private_key_file_file* *private_key_file*
The path to the private key file for TLS encryption. Only applicable The path to the private key file for encryption. Only applicable when
when *enable_auth*=true. *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* *username*
Choose a username for authentication. Choose a username for authentication.
*use_relative_paths*
Make file paths relative to the location of the config file.
*xkb_layout* *xkb_layout*
The keyboard layout to use for key code lookup. The keyboard layout to use for key code lookup.
@ -162,14 +148,12 @@ considered to be part of the key or the value.
## EXAMPLE ## EXAMPLE
``` ```
use_relative_paths=true
address=0.0.0.0 address=0.0.0.0
enable_auth=true enable_auth=true
username=luser username=luser
password=p455w0rd password=p455w0rd
rsa_private_key_file=rsa_key.pem private_key_file=/path/to/key.pem
private_key_file=tls_key.pem certificate_file=/path/to/cert.pem
certificate_file=tls_cert.pem
``` ```
# WAYVNCCTL CONTROL SOCKET # WAYVNCCTL CONTROL SOCKET
@ -189,6 +173,13 @@ available commands.
If an optional *command* parameter refers to one of those commands by name, the 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. response data will be a detailed description of that command and its parameters.
_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.
_EVENT-RECEIVE_ _EVENT-RECEIVE_
The *event-receive* command registers for asynchronous server events. See the The *event-receive* command registers for asynchronous server events. See the
@ -199,14 +190,22 @@ Event registration registers for all available server events and is scoped to
the current connection only. If a client disconnects and reconnects, it must the current connection only. If a client disconnects and reconnects, it must
re-register for events. re-register for events.
_CLIENT-LIST_ _SET-OUTPUT_
The *client-list* command retrieves a list of all VNC clients currently For multi-output wayland displays, this command switches which output is
connected to wayvnc. actively captured by wayvnc. This operates in 2 different modes, depending on
which parameters are supplied:
_CLIENT-DISCONNECT_ *cycle=next|prev*
Cycle to the next/prev output in the output list, wrapping back to the
first/last if the end of the list is reached.
The *client-disconnect* command disconnects a single VNC client. *switch-to=output-name*
Switch to a specific output by name.
_DISCONNECT_CLIENT_
The *disconnect-client* command disconnects a single VNC client.
Parameters: Parameters:
@ -214,49 +213,22 @@ Parameters:
Required: The ID of the client to disconnect. This ID can be found from the 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. _GET-CLIENTS_ command or receipt of a _CLIENT-CONNECTED_ event.
_OUTPUT-LIST_ _WAYVNC_EXIT_
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. The *wayvnc-exit* command disconnects all clients and shuts down wayvnc.
_GET-CLIENTS_
The *get-clients* command retrieves a list of all VNC clients currently
connected to wayvnc.
_GET-OUTPUTS_
The *get-outputs* command retrieves a list of all outputs known to wayvnc and
whether or not each one is currently being captured.
## IPC EVENTS ## 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_ _CLIENT-CONNECTED_
The *client-connected* event is sent when a new VNC client connects to wayvnc. The *client-connected* event is sent when a new VNC client connects to wayvnc.
@ -269,8 +241,8 @@ Parameters:
*connection_count=...* *connection_count=...*
The total number of connected VNC clients including this one. The total number of connected VNC clients including this one.
*address=...* *hostname=...*
The IP address of this client. May be null. The hostname or IP of this client. May be null.
*username=...* *username=...*
The username used to authenticate this client. May be null. The username used to authenticate this client. May be null.
@ -288,12 +260,22 @@ Parameters:
*connection_count=...* *connection_count=...*
The total number of connected VNC clients not including this one. The total number of connected VNC clients not including this one.
*address=...* *hostname=...*
The IP address of this client. May be null. The hostname or IP of this client. May be null.
*username=...* *username=...*
The username used to authenticate this client. May be null. The username used to authenticate this client. May be null.
_CAPTURE_CHANGED_
The *capture-changed* event is sent when the currently captured output
changes.
Parameters:
*output=...*
The name of the output now being captured.
## IPC MESSAGE FORMAT ## IPC MESSAGE FORMAT
The *wayvncctl(1)* command line utility will construct properly-formatted json The *wayvncctl(1)* command line utility will construct properly-formatted json

View File

@ -40,15 +40,17 @@ wayvncctl - A command line control client for wayvnc(1)
*wayvnc(1)* allows runtime interaction via a unix socket json-ipc mechanism. *wayvnc(1)* allows runtime interaction via a unix socket json-ipc mechanism.
This command line utility provides easy interaction with those commands. This command line utility provides easy interaction with those commands.
This command is largely self-documenting: For a full list of currently supported commands, see
*wayvnc(1)* section _IPC COMMANDS_, or run the
*wayvncctl help* command.
- Running *wayvncctl --help* lists all supported IPC commands. Running *wayvncctl help* returns a list of the available commands and events.
- Running *wayvncctl command-name --help* returns a description of the given
Running *wayvncctl command-name --help* returns a description of the server-side
command and its available parameters. command and its available parameters.
- Running *wayvncctl event-receive --help* includes a list of all supported event
names. Running *wayvncctl help --event=event-name* returns a description of the
- Running *wayvncctl event-receive --show=event-name* returns a server-side event and expected parameters.
description of the given event and expected data fields.
# ASYNCHRONOUS EVENTS # ASYNCHRONOUS EVENTS
@ -63,8 +65,8 @@ the end, for ease in scripting:
``` ```
$ wayvncctl --json event-receive $ wayvncctl --json event-receive
{"method":"client-connected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":1}} {"method":"client-connected","params":{"id":"0x10ef670","hostname":null,"username":null,"connection_count":1}}
{"method":"client-disconnected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":0}} {"method":"client-disconnected","params":{"id":"0x10ef670","hostname":null,"username":null,"connection_count":0}}
``` ```
The default human-readible output is a multi-line yaml-like format, with two The default human-readible output is a multi-line yaml-like format, with two
@ -75,12 +77,12 @@ $ wayvncctl event-receive
client-connected: client-connected:
id: 0x10ef670 id: 0x10ef670
address: 192.168.1.18 hostname: 192.168.1.18
connection_count: 1 connection_count: 1
client-disconnected: client-disconnected:
id: 0x10ef670 id: 0x10ef670
address: 192.168.1.18 hostname: 192.168.1.18
connection_count: 0 connection_count: 0
``` ```
@ -105,18 +107,52 @@ generate 2 additional events not documented in *wayvnc(1)*:
# EXAMPLES # EXAMPLES
Get help on the "output-set" IPC command: Query the server for all available IPC command names:
``` ```
$ wayvncctl output-set --help $ wayvncctl help
Usage: wayvncctl [options] output-set <output-name> [params] Commands:
... - help
- version
- event-receive
- set-output
- get-clients
- get-outputs
- disconnect-client
- wayvnc-exit
Run 'wayvncctl command-name --help' for command-specific details.
Events:
- client-connected
- client-disconnected
- capture-changed
Run 'wayvncctl help --event=event-name' for event-specific details.
```
Get help on the "set-output" IPC command:
```
$ wayvncctl set-output --help
Usage: wayvncctl [options] set-output [params]
Switch the actively captured output
Parameters:
--switch-to=...
The specific output name to capture
--cycle=...
Either "next" or "prev"
Run 'wayvncctl --help' for allowed options
``` ```
Cycle to the next active output: Cycle to the next active output:
``` ```
$ wayvncctl output-cycle $ wayvncctl set-output --cycle=next
``` ```
Get json-formatted version information: Get json-formatted version information: