Compare commits

...

46 Commits
v0.7 ... master

Author SHA1 Message Date
Jonas Letzbor 6dca473059
Fix open-h264 encoding for AMD GPU 2024-06-12 00:03:21 +02:00
Jonas Letzbor ffa0a56335
Add (uncommented) workaround for locale cursor rendering in xwayland 2024-06-11 22:22:27 +02:00
Andri Yngvason 50f095d6e8 Initialise VNC last
This fixes deinitialisation ordering and makes it so that we don't start
listening until everything else is initialised.
2024-06-04 21:09:05 +00:00
Attila Fidan b7de0d9fa6 main: Use info.address_storage to compose client addr strings
Otherwise, getpeername() will truncate IPv6 client addresses and any
IPv6 clients the control client receives information about in the JSON
response will have the second half of their addresses zeroed out.
2024-06-02 10:14:27 +00:00
Attila Fidan f970c5ceb7 FAQ: Add example disabling floating_modifier in passthrough mode 2024-06-02 10:13:52 +00:00
Simon Ser 3c596455e8 meson: Specify check arg in run_command()
Fixes the following warning:

    WARNING: You should add the boolean check kwarg to the run_command call.
             It currently defaults to false,
             but it will default to true in future releases of meson.
             See also: https://github.com/mesonbuild/meson/issues/9300
2024-04-19 22:24:59 +00:00
Andri Yngvason 15660cd4a7 keyboard: Pass keyboard LED state to client 2024-04-07 12:31:34 +00:00
Andri Yngvason fbd98edae9 README: Update builds.sr.ht badge path
It changed...
2024-03-30 17:14:15 +00:00
Andri Yngvason 56c38af25f buffer: Only include linux/dma-heap.h where available
This unbreaks the build on FreeBSD.
2024-03-30 17:04:53 +00:00
Andri Yngvason 333381326d test: integration: Disable multi-output test
It seems rather fragile and I don't have the time to make it more robust.
2024-03-30 16:39:33 +00:00
Andri Yngvason 17841f9ece FUNDING.yml: Add github sponsors 2024-03-26 10:35:30 +00:00
Andri Yngvason b292c086fe buffer: Align CMA buffers up to nearest multiple of 16 2024-03-16 22:33:09 +00:00
Andri Yngvason 54fb881aab util: Add ALIGN_UP macro 2024-03-16 16:46:14 +00:00
Andri Yngvason bd9daa85c3 buffer: Allocate DMA-BUFs via CMA where available 2024-03-10 17:57:34 +00:00
Jan Beich 5b01551673 ctl: Add missing header after 42494fbbe4
src/ctl-server.c:373:36: error: incomplete definition of type 'struct sockaddr_in'
                inet_ntop(addr->sa_family, &sa_in->sin_addr, dst, sz);
                                            ~~~~~^
src/ctl-server.c:368:9: note: forward declaration of 'struct sockaddr_in'
        struct sockaddr_in* sa_in = (struct sockaddr_in*)addr;
               ^
src/ctl-server.c:376:37: error: incomplete definition of type 'struct sockaddr_in6'
                inet_ntop(addr->sa_family, &sa_in6->sin6_addr, dst, sz);
                                            ~~~~~~^
src/ctl-server.c:369:9: note: forward declaration of 'struct sockaddr_in6'
        struct sockaddr_in6* sa_in6 = (struct sockaddr_in6*)addr;
               ^
2024-02-26 12:37:19 +00:00
Andri Yngvason d71bca5270 meson: Bump version to 0.9 2024-02-25 12:11:31 +00:00
Andri Yngvason 5d55944dab ctl: Emit event when output is added/removed 2024-02-11 22:17:50 +00:00
Andri Yngvason d819ca477c README: Use ECDSA instead of RSA 2024-01-10 22:43:05 +00:00
Andri Yngvason 42494fbbe4 ctl: Replace hostname with IP address 2023-12-31 18:06:35 +00:00
Andri Yngvason 5ef322701e main: Fix crashes with non-wlroots compositors 2023-12-26 15:10:29 +00:00
Andri Yngvason fb4c5c55a1 main: Pass error messages to control socket 2023-12-26 15:06:05 +00:00
Andri Yngvason a7ed782bb9 main: Fix ctl-server use after free 2023-12-10 19:03:26 +00:00
Andri Yngvason 830142701a pam_auth: Correct use of calloc() 2023-12-10 18:51:58 +00:00
Andri Yngvason fcfd4280b7 output-management: Clean up and init resources on destroy
If we leave these dangling valgrind will complain and thing will be left
in a bad state for re-attachment.
2023-12-10 18:29:06 +00:00
Andri Yngvason a34a2d1cb9 main: Remove wayland fd handler on detach 2023-12-10 18:14:24 +00:00
Andri Yngvason f2c9ab0fb5 output-management: Force normal transform on headless 2023-11-19 21:55:58 +00:00
Andri Yngvason 8dbce970c0 output: NOOP is another name for HEADLESS 2023-11-19 21:38:48 +00:00
Andri Yngvason 11a477f92e main: Only one client is allowed to resize 2023-11-19 21:17:15 +00:00
Consolatis 6e1d8aaddc Automatically resize headless outputs on client request 2023-11-19 21:06:41 +00:00
Consolatis ac907a07f3 Add wlr-output-management protocol 2023-11-19 21:01:45 +00:00
Andri Yngvason 64497d9388 Detach instead of exiting if all outputs go away 2023-11-19 20:47:39 +00:00
Andri Yngvason e0bdf98112 builds: archlinux: Add python cryptodomex
This is needed by vncdo
2023-11-19 20:47:39 +00:00
Andri Yngvason e71956769c examples: Add script to auto attach to any compositor 2023-11-19 20:47:39 +00:00
Andri Yngvason 27a8008edf Emit "detached" control event 2023-11-19 20:47:39 +00:00
Andri Yngvason 3af4851e4d Detach instead of exiting when compositor goes away 2023-11-19 20:47:39 +00:00
Andri Yngvason 9647d1089a Implement detached mode
This makes it possible to attach to a compositor at a later time and to
detach and re-attach.
2023-11-19 20:47:39 +00:00
Andri Yngvason 6470dfef5f Relax auth parameter sanitation
Since more authentication modes have been added, it's no longer a
requirement to have key files set.
2023-11-05 21:13:30 +00:00
Lucas Servén Marín b4c234b3fa man: document websocket flag
Signed-off-by: Lucas Servén Marín <lserven@gmail.com>
2023-11-05 20:51:15 +00:00
Andri Yngvason a53ff1f769 Sanitise buffer damage from compositor
Some compositors fail to send properly bounded damage regions. Let's
not crash when that happens.
2023-11-02 22:42:17 +00:00
Andri Yngvason 27cd6351be ctl-server: Add null-check for info.seat 2023-10-14 10:43:08 +00:00
Christopher Snowhill 57fb350cfe Add null seat pointer checks 2023-10-14 10:40:57 +00:00
Andri Yngvason 1c489ad8e9 Fix copy-pasta from last commit 2023-10-11 09:21:53 +00:00
Andri Yngvason 301b4c0b53 Add transient seats 2023-10-10 22:38:58 +00:00
Andri Yngvason c1281dad60 Don't use tag for git version 2023-10-09 22:57:36 +00:00
Andri Yngvason b5128070ae meson: Bump Neat VNC version requirement 2023-10-04 22:56:17 +00:00
Andri Yngvason 10a8dbe167 Set version for next release 2023-10-04 22:47:04 +00:00
30 changed files with 2106 additions and 189 deletions

View File

@ -14,6 +14,7 @@ packages:
- sway
- jq
- lsof
- python-pycryptodomex # needed by vncdotool
- vncdotool
sources:

1
.gitignore vendored
View File

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

10
FAQ.md
View File

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

View File

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

View File

@ -1,7 +1,7 @@
# wayvnc
[![Build and Unit Test](https://github.com/any1/wayvnc/actions/workflows/build.yml/badge.svg)](https://github.com/any1/wayvnc/actions/workflows/build.yml)
[![builds.sr.ht status](https://builds.sr.ht/~andri/wayvnc/commits/master.svg)](https://builds.sr.ht/~andri/wayvnc/commits/master?)
[![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
@ -122,8 +122,9 @@ For TLS, you'll need a private X509 key and a certificate. A self-signed key
with a certificate can be generated like so:
```
cd ~/.config/wayvnc
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout tls_key.pem -out tls_cert.pem -subj /CN=localhost \
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -sha384 \
-days 3650 -nodes -keyout tls_key.pem -out tls_cert.pem \
-subj /CN=localhost \
-addext subjectAltName=DNS:localhost,DNS:localhost,IP:127.0.0.1
cd -
```

View File

@ -0,0 +1,88 @@
#!/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,5 +1,6 @@
/*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -19,6 +20,8 @@
#include <stdbool.h>
enum cmd_type {
CMD_ATTACH,
CMD_DETACH,
CMD_HELP,
CMD_EVENT_RECEIVE,
CMD_CLIENT_LIST,
@ -36,6 +39,9 @@ enum event_type {
EVT_CAPTURE_CHANGED,
EVT_CLIENT_CONNECTED,
EVT_CLIENT_DISCONNECTED,
EVT_DETACHED,
EVT_OUTPUT_ADDED,
EVT_OUTPUT_REMOVED,
EVT_UNKNOWN,
};
#define EVT_LIST_LEN EVT_UNKNOWN

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,6 +19,8 @@
#include "output.h"
#include <sys/socket.h>
struct ctl;
struct cmd_response;
@ -25,9 +28,12 @@ struct ctl_server_client;
struct ctl_server_client_info {
int id;
const char *hostname;
const char *username;
const char *seat;
union {
struct sockaddr_storage address_storage;
struct sockaddr address;
};
const char* username;
const char* seat;
};
struct ctl_server_output {
@ -41,6 +47,8 @@ struct ctl_server_output {
struct ctl_server_actions {
void* userdata;
struct cmd_response* (*on_attach)(struct ctl*, const char* display);
struct cmd_response* (*on_detach)(struct ctl*);
struct cmd_response* (*on_output_cycle)(struct ctl*,
enum output_cycle_direction direction);
struct cmd_response* (*on_output_switch)(struct ctl*,
@ -79,3 +87,8 @@ void ctl_server_event_disconnected(struct ctl*,
void ctl_server_event_capture_changed(struct ctl*,
const char* captured_output);
void ctl_server_event_detached(struct ctl*);
void ctl_server_event_output_added(struct ctl*, const char* name);
void ctl_server_event_output_removed(struct ctl*, const char* name);

View File

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

View File

@ -0,0 +1,28 @@
/*
* 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,6 +57,7 @@ struct output {
bool is_dimension_changed;
bool is_transform_changed;
bool is_headless;
void (*on_dimension_change)(struct output*);
void (*on_transform_change)(struct output*);

View File

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

View File

@ -1,7 +1,7 @@
project(
'wayvnc',
'c',
version: '0.6.0',
version: '0.9-dev',
license: 'ISC',
default_options: [
'c_std=gnu11',
@ -14,26 +14,30 @@ host_system = host_machine.system()
prefix = get_option('prefix')
c_args = [
'-DPROJECT_VERSION="@0@"'.format(meson.project_version()),
'-D_GNU_SOURCE',
'-DAML_UNSTABLE_API=1',
'-DWLR_USE_UNSTABLE=true',
'-Wno-unused-parameter',
'-Wno-missing-field-initializers',
]
version = '"@0@"'.format(meson.project_version())
git = find_program('git', native: true, required: false)
if git.found()
git_describe = run_command([git, 'describe', '--tags', '--long'])
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'])
if git_describe.returncode() == 0 and git_branch.returncode() == 0
c_args += '-DGIT_VERSION="@0@ (@1@)"'.format(
git_describe.stdout().strip(),
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
if git_commit.returncode() == 0 and git_branch.returncode() == 0
version = '"v@0@-@1@ (@2@)"'.format(
meson.project_version(),
git_commit.stdout().strip(),
git_branch.stdout().strip(),
)
endif
endif
add_project_arguments('-DPROJECT_VERSION=@0@'.format(version), language: 'c')
if buildtype != 'debug' and buildtype != 'debugoptimized'
c_args += '-DNDEBUG'
endif
@ -50,11 +54,18 @@ pixman = dependency('pixman-1')
gbm = dependency('gbm', required: get_option('screencopy-dmabuf'))
drm = dependency('libdrm')
xkbcommon = dependency('xkbcommon', version: '>=1.0.0')
wayland_server = dependency('wayland-server')
wayland_client = dependency('wayland-client')
wayland_client_protocol = dependency('wayland-protocols')
wayland_cursor = dependency('wayland-cursor')
jansson = dependency('jansson')
# Cursor image
x11_dep = dependency('x11')
x11_fixes_dep = dependency('xfixes')
aml_version = ['>=0.3.0', '<0.4.0']
neatvnc_version = ['>=0.6.0', '<0.7.0']
neatvnc_version = ['>=0.9', '<0.10.0']
neatvnc_project = subproject(
'neatvnc',
@ -75,7 +86,7 @@ else
neatvnc = dependency('neatvnc', version: neatvnc_version)
endif
inc = include_directories('include')
inc = include_directories('include', '/usr/include/wlroots0.16')
subdir('protocols')
@ -86,6 +97,7 @@ sources = [
'src/screencopy.c',
'src/data-control.c',
'src/output.c',
'src/output-management.c',
'src/pointer.c',
'src/keyboard.c',
'src/seat.c',
@ -115,6 +127,11 @@ dependencies = [
xkbcommon,
client_protos,
jansson,
x11_dep,
x11_fixes_dep,
wayland_client_protocol,
wayland_cursor,
wayland_server
]
ctlsources = [
@ -140,6 +157,10 @@ if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt
config.set('HAVE_USDT', true)
endif
if cc.has_header('linux/dma-heap.h') and cc.has_header('linux/dma-buf.h')
config.set('HAVE_LINUX_DMA_HEAP', true)
endif
if cc.has_function('memfd_create')
config.set('HAVE_MEMFD', true)
config.set('HAVE_MEMFD_CREATE', true)

View File

@ -0,0 +1,110 @@
<?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,7 +21,9 @@ client_protocols = [
'xdg-output-unstable-v1.xml',
'linux-dmabuf-unstable-v1.xml',
'wlr-data-control-unstable-v1.xml',
'wlr-output-management-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml',
'ext-transient-seat-v1.xml',
]
client_protos_src = []

View File

@ -0,0 +1,601 @@
<?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 - 2021 Andri Yngvason
* Copyright (c) 2020 - 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -31,10 +31,20 @@
#include "buffer.h"
#include "pixels.h"
#include "config.h"
#include "util.h"
#ifdef ENABLE_SCREENCOPY_DMABUF
#include <gbm.h>
#endif
#include <sys/ioctl.h>
#include <fcntl.h>
// #ifdef HAVE_LINUX_DMA_HEAP
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#define LINUX_CMA_PATH "/dev/dma_heap/linux,cma"
//#endif // HAVE_LINUX_DMA_HEAP
#endif // ENABLE_SCREENCOPY_DMABUF
extern struct wl_shm* wl_shm;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
@ -118,6 +128,81 @@ failure:
}
#ifdef ENABLE_SCREENCOPY_DMABUF
#ifdef HAVE_LINUX_DMA_HEAP
static bool have_linux_cma(void)
{
return access(LINUX_CMA_PATH, R_OK | W_OK) == 0;
}
static int linux_cma_alloc(size_t size)
{
int fd = open(LINUX_CMA_PATH, O_RDWR | O_CLOEXEC, 0);
if (fd < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to open CMA device: %m");
return -1;
}
struct dma_heap_allocation_data data = {
.len = size,
.fd_flags = O_CLOEXEC | O_RDWR,
};
int r = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
if (r < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate CMA buffer: %m");
return -1;
}
close(fd);
return data.fd;
}
// Some devices (mostly ARM SBCs) need CMA for hardware encoders.
static struct gbm_bo* create_cma_gbm_bo(int width, int height, uint32_t fourcc)
{
assert(gbm_device);
int bpp = pixel_size_from_fourcc(fourcc);
if (!bpp) {
nvnc_log(NVNC_LOG_PANIC, "Unsupported pixel format: %" PRIu32,
fourcc);
}
/* TODO: Get alignment through feedback mechanism.
* Buffer sizes are aligned on both axes by 16 and we'll do the same
* in the encoder, but this requirement should come from the encoder.
*/
int stride = bpp * ALIGN_UP(width, 16);
int fd = linux_cma_alloc(stride * ALIGN_UP(height, 16));
if (fd < 0) {
return NULL;
}
struct gbm_import_fd_modifier_data d = {
.format = fourcc,
.width = width,
.height = height,
// v4l2m2m doesn't support modifiers, so we use linear
.modifier = DRM_FORMAT_MOD_LINEAR,
.num_fds = 1,
.fds[0] = fd,
.offsets[0] = 0,
.strides[0] = stride,
};
struct gbm_bo* bo = gbm_bo_import(gbm_device, GBM_BO_IMPORT_FD_MODIFIER,
&d, 0);
if (!bo) {
nvnc_log(NVNC_LOG_DEBUG, "Failed to import dmabuf: %m");
close(fd);
return NULL;
}
return bo;
}
#endif // HAVE_LINUX_DMA_HEAP
static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
uint32_t fourcc)
{
@ -133,8 +218,17 @@ static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
self->height = height;
self->format = fourcc;
self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
GBM_BO_USE_RENDERING);
// Checks not needed anymore. Fixed with SCANOUT and within neatvnc for most GPUs.
// But this could still fail!
//#ifdef HAVE_LINUX_DMA_HEAP
self->bo = have_linux_cma() ?
create_cma_gbm_bo(width, height, fourcc) :
gbm_bo_create(gbm_device, width, height, fourcc,
GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
//#endif
// self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
// GBM_BO_USE_RENDERING);
if (!self->bo)
goto bo_failure;

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -353,17 +354,17 @@ static void pretty_client_list(json_t* data)
json_t* value;
json_array_foreach(data, i, value) {
char* id = NULL;
char* hostname = NULL;
char* address = NULL;
char* username = NULL;
json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "hostname",
&hostname, "username", &username);
json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "address",
&address, "username", &username);
printf(" %s: ", id);
if (username)
printf("%s@", username);
printf("%s\n", hostname ? hostname : "<unknown>");
printf("%s\n", address ? address : "<unknown>");
}
}
@ -403,6 +404,8 @@ static void pretty_print(json_t* data,
case CMD_OUTPUT_LIST:
pretty_output_list(data);
break;
case CMD_ATTACH:
case CMD_DETACH:
case CMD_CLIENT_DISCONNECT:
case CMD_OUTPUT_SET:
case CMD_OUTPUT_CYCLE:

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -20,6 +21,19 @@
#include <string.h>
struct cmd_info ctl_command_list[] = {
[CMD_ATTACH] = { "attach",
"Attach to a running wayland compositor",
{
{ "display", "Display name", "<name>",
.positional = true },
{},
}
},
[CMD_DETACH] = { "detach",
"Detach from the wayland compositor",
{{}},
},
[CMD_HELP] = { "help",
"List all commands and events, or show usage of a specific command or event",
{
@ -84,8 +98,8 @@ struct cmd_info ctl_command_list[] = {
{ "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)", \
{ "address", \
"The IP address of this client (may be null)", \
"<name|ip>" }, \
{ "username", \
"The username used to authentice this client (may be null).", \
@ -110,6 +124,24 @@ struct cmd_info ctl_event_list[] = {
"Sent by waynvc when a VNC client disconnects",
{ CLIENT_EVENT_PARAMS("not including") }
},
[EVT_DETACHED] = {"detached",
"Sent after detaching from compositor",
{}
},
[EVT_OUTPUT_ADDED] = {"output-added",
"Sent when an output is added by the compositor",
{
{ "name", "Output name", "<string>" },
{}
}
},
[EVT_OUTPUT_REMOVED] = {"output-removed",
"Sent when an output is removed by the compositor",
{
{ "name", "Output name", "<string>" },
{}
}
},
};
enum cmd_type ctl_command_parse_name(const char* name)

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -26,6 +27,8 @@
#include <neatvnc.h>
#include <aml.h>
#include <jansson.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "output.h"
#include "ctl-commands.h"
@ -46,6 +49,11 @@ struct cmd {
enum cmd_type type;
};
struct cmd_attach {
struct cmd cmd;
char display[128];
};
struct cmd_help {
struct cmd cmd;
char id[64];
@ -105,6 +113,19 @@ static void cmd_response_destroy(struct cmd_response* 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,
struct jsonipc_error* err)
{
@ -185,6 +206,9 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
enum cmd_type cmd_type = ctl_command_parse_name(ipc->method);
struct cmd* cmd = NULL;
switch (cmd_type) {
case CMD_ATTACH:
cmd = (struct cmd*)cmd_attach_new(ipc->params, err);
break;
case CMD_HELP:
cmd = (struct cmd*)cmd_help_new(ipc->params, err);
break;
@ -194,6 +218,7 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
case CMD_CLIENT_DISCONNECT:
cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err);
break;
case CMD_DETACH:
case CMD_VERSION:
case CMD_EVENT_RECEIVE:
case CMD_CLIENT_LIST:
@ -339,6 +364,25 @@ static struct ctl_server_client* ctl_server_client_next(struct ctl* self,
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)
@ -361,15 +405,21 @@ static struct cmd_response* generate_vnc_client_list(struct ctl* self)
snprintf(id_str, sizeof(id_str), "%d", info.id);
json_t* packed = json_pack("{s:s}", "id", id_str);
if (info.hostname)
json_object_set_new(packed, "hostname",
json_string(info.hostname));
char address_string[256];
if (sockaddr_to_string(address_string, sizeof(address_string),
&info.address) == 0) {
json_object_set_new(packed, "address",
json_string(address_string));
}
if (info.username)
json_object_set_new(packed, "username",
json_string(info.username));
json_object_set_new(packed, "seat", json_string(info.seat));
if (info.seat)
json_object_set_new(packed, "seat",
json_string(info.seat));
json_array_append_new(response->data, packed);
}
@ -404,6 +454,11 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name);
struct cmd_response* response = NULL;
switch (cmd->type) {
case CMD_ATTACH:{
struct cmd_attach* c = (struct cmd_attach*)cmd;
response = self->actions.on_attach(self, c->display);
break;
}
case CMD_HELP:{
struct cmd_help* c = (struct cmd_help*)cmd;
response = generate_help_object(c->id, c->id_is_command);
@ -420,6 +475,9 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
response = self->actions.on_disconnect_client(self, c->id);
break;
}
case CMD_DETACH:
response = self->actions.on_detach(self);
break;
case CMD_WAYVNC_EXIT:
response = self->actions.on_wayvnc_exit(self);
break;
@ -863,9 +921,13 @@ json_t* pack_connection_event_params(
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info->id);
char address_string[256];
bool have_addr = sockaddr_to_string(address_string,
sizeof(address_string), &info->address) == 0;
return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}",
"id", id_str,
"hostname", info->hostname,
"address", have_addr ? address_string : NULL,
"username", info->username,
"seat", info->seat,
"connection_count", new_connection_count);
@ -939,3 +1001,20 @@ void ctl_server_event_capture_changed(struct ctl* self,
ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED,
json_pack("{s:s}", "output", captured_output));
}
void ctl_server_event_detached(struct ctl* self)
{
ctl_server_enqueue_event(self, EVT_DETACHED, json_object());
}
void ctl_server_event_output_added(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_ADDED,
json_pack("{s:s}", "name", name));
}
void ctl_server_event_output_removed(struct ctl* self, const char* name)
{
ctl_server_enqueue_event(self, EVT_OUTPUT_REMOVED,
json_pack("{s:s}", "name", name));
}

View File

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

@ -0,0 +1,283 @@
/*
* 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,7 +219,12 @@ void output_name(void* data, struct zxdg_output_v1* xdg_output,
struct output* self = data;
strlcpy(self->name, name, sizeof(self->name));
nvnc_trace("Output %u name: %s", self->id, self->name);
self->is_headless =
(strncmp(name, "HEADLESS-", strlen("HEADLESS-")) == 0) ||
(strncmp(name, "NOOP-", strlen("NOOP-")) == 0);
nvnc_trace("Output %u name: %s, headless: %s", self->id, self->name,
self->is_headless ? "yes" : "no");
}
void output_description(void* data, struct zxdg_output_v1* xdg_output,

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 credentials* cred = appdata_ptr;
struct pam_response* resp = calloc(sizeof(*response), num_msg);
struct pam_response* resp = calloc(num_msg, sizeof(*resp));
for (int i = 0; i < num_msg; i++) {
resp[i].resp_retcode = PAM_SUCCESS;
switch(msgm[i]->msg_style) {

View File

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

View File

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

View File

@ -377,4 +377,4 @@ multioutput_test() {
}
smoke_test
multioutput_test
#multioutput_test

View File

@ -52,6 +52,9 @@ wayvnc - A VNC server for wlroots based Wayland compositors.
*-v,--verbose*
Be more verbose. Same as setting `--log-level=info`.
*-w,--websocket*
Create a websocket.
*-L,--log-level*
Set log level. The levels are: error, warning, info, debug, trace and
quiet.
@ -266,8 +269,8 @@ Parameters:
*connection_count=...*
The total number of connected VNC clients including this one.
*hostname=...*
The hostname or IP of this client. May be null.
*address=...*
The IP address of this client. May be null.
*username=...*
The username used to authenticate this client. May be null.
@ -285,8 +288,8 @@ Parameters:
*connection_count=...*
The total number of connected VNC clients not including this one.
*hostname=...*
The hostname or IP of this client. May be null.
*address=...*
The IP address of this client. May be null.
*username=...*
The username used to authenticate this client. May be null.

View File

@ -63,8 +63,8 @@ the end, for ease in scripting:
```
$ wayvncctl --json event-receive
{"method":"client-connected","params":{"id":"0x10ef670","hostname":null,"username":null,"connection_count":1}}
{"method":"client-disconnected","params":{"id":"0x10ef670","hostname":null,"username":null,"connection_count":0}}
{"method":"client-connected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":1}}
{"method":"client-disconnected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":0}}
```
The default human-readible output is a multi-line yaml-like format, with two
@ -75,12 +75,12 @@ $ wayvncctl event-receive
client-connected:
id: 0x10ef670
hostname: 192.168.1.18
address: 192.168.1.18
connection_count: 1
client-disconnected:
id: 0x10ef670
hostname: 192.168.1.18
address: 192.168.1.18
connection_count: 0
```