Compare commits

..

25 Commits

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
20 changed files with 459 additions and 70 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ perf.*
*.pem *.pem
.vimrc .vimrc
.cache .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 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 +1,2 @@
github: any1
patreon: andriyngvason patreon: andriyngvason

View File

@ -1,7 +1,7 @@
# 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) [![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) [![Packaging status](https://repology.org/badge/tiny-repos/wayvnc.svg)](https://repology.org/project/wayvnc/versions)
## Introduction ## 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: with a certificate can be generated like so:
``` ```
cd ~/.config/wayvnc 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 tls_key.pem -out tls_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 - cd -
``` ```

View File

@ -40,6 +40,8 @@ enum event_type {
EVT_CLIENT_CONNECTED, EVT_CLIENT_CONNECTED,
EVT_CLIENT_DISCONNECTED, EVT_CLIENT_DISCONNECTED,
EVT_DETACHED, EVT_DETACHED,
EVT_OUTPUT_ADDED,
EVT_OUTPUT_REMOVED,
EVT_UNKNOWN, EVT_UNKNOWN,
}; };
#define EVT_LIST_LEN EVT_UNKNOWN #define EVT_LIST_LEN EVT_UNKNOWN

View File

@ -19,6 +19,8 @@
#include "output.h" #include "output.h"
#include <sys/socket.h>
struct ctl; struct ctl;
struct cmd_response; struct cmd_response;
@ -26,9 +28,12 @@ struct ctl_server_client;
struct ctl_server_client_info { struct ctl_server_client_info {
int id; int id;
const char *hostname; union {
const char *username; struct sockaddr_storage address_storage;
const char *seat; struct sockaddr address;
};
const char* username;
const char* seat;
}; };
struct ctl_server_output { struct ctl_server_output {
@ -84,3 +89,6 @@ 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_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 <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;
@ -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(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

@ -19,6 +19,7 @@
#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.8-dev', version: '0.9-dev',
license: 'ISC', license: 'ISC',
default_options: [ default_options: [
'c_std=gnu11', 'c_std=gnu11',
@ -16,6 +16,7 @@ prefix = get_option('prefix')
c_args = [ c_args = [
'-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',
@ -24,8 +25,8 @@ c_args = [
version = '"@0@"'.format(meson.project_version()) 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']) git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
if git_commit.returncode() == 0 and git_branch.returncode() == 0 if git_commit.returncode() == 0 and git_branch.returncode() == 0
version = '"v@0@-@1@ (@2@)"'.format( version = '"v@0@-@1@ (@2@)"'.format(
meson.project_version(), meson.project_version(),
@ -53,11 +54,18 @@ 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
x11_dep = dependency('x11')
x11_fixes_dep = dependency('xfixes')
aml_version = ['>=0.3.0', '<0.4.0'] aml_version = ['>=0.3.0', '<0.4.0']
neatvnc_version = ['>=0.7.0', '<0.9.0'] neatvnc_version = ['>=0.9', '<0.10.0']
neatvnc_project = subproject( neatvnc_project = subproject(
'neatvnc', 'neatvnc',
@ -78,7 +86,7 @@ else
neatvnc = dependency('neatvnc', version: neatvnc_version) neatvnc = dependency('neatvnc', version: neatvnc_version)
endif endif
inc = include_directories('include') inc = include_directories('include', '/usr/include/wlroots0.16')
subdir('protocols') subdir('protocols')
@ -119,6 +127,11 @@ dependencies = [
xkbcommon, xkbcommon,
client_protos, client_protos,
jansson, jansson,
x11_dep,
x11_fixes_dep,
wayland_client_protocol,
wayland_cursor,
wayland_server
] ]
ctlsources = [ ctlsources = [
@ -144,6 +157,10 @@ 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

@ -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 * 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,10 +31,20 @@
#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>
#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 wl_shm* wl_shm;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf; extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
@ -118,6 +128,81 @@ 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)
{ {
@ -133,8 +218,17 @@ static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
self->height = height; self->height = height;
self->format = fourcc; self->format = fourcc;
self->bo = gbm_bo_create(gbm_device, width, height, fourcc, // Checks not needed anymore. Fixed with SCANOUT and within neatvnc for most GPUs.
GBM_BO_USE_RENDERING); // 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) if (!self->bo)
goto bo_failure; goto bo_failure;

View File

@ -354,17 +354,17 @@ static void pretty_client_list(json_t* data)
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* hostname = NULL; char* address = 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(" %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>");
} }
} }

View File

@ -98,8 +98,8 @@ struct cmd_info ctl_command_list[] = {
{ "connection_count", \ { "connection_count", \
"The total number of connected VNC clients " including " this one.", \ "The total number of connected VNC clients " including " this one.", \
"<integer>" }, \ "<integer>" }, \
{ "hostname", \ { "address", \
"The hostname or IP address of this client (may be null)", \ "The IP address of this client (may be null)", \
"<name|ip>" }, \ "<name|ip>" }, \
{ "username", \ { "username", \
"The username used to authentice this client (may be null).", \ "The username used to authentice this client (may be null).", \
@ -128,6 +128,20 @@ struct cmd_info ctl_event_list[] = {
"Sent after detaching from compositor", "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) enum cmd_type ctl_command_parse_name(const char* name)

View File

@ -27,6 +27,8 @@
#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"
@ -362,6 +364,25 @@ static struct ctl_server_client* ctl_server_client_next(struct ctl* self,
return self->actions.client_next(self, 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, static void ctl_server_client_get_info(struct ctl* self,
const struct ctl_server_client* client, const struct ctl_server_client* client,
struct ctl_server_client_info* info) struct ctl_server_client_info* info)
@ -384,9 +405,12 @@ static struct cmd_response* generate_vnc_client_list(struct ctl* self)
snprintf(id_str, sizeof(id_str), "%d", info.id); snprintf(id_str, sizeof(id_str), "%d", info.id);
json_t* packed = json_pack("{s:s}", "id", id_str); json_t* packed = json_pack("{s:s}", "id", id_str);
if (info.hostname) char address_string[256];
json_object_set_new(packed, "hostname", if (sockaddr_to_string(address_string, sizeof(address_string),
json_string(info.hostname)); &info.address) == 0) {
json_object_set_new(packed, "address",
json_string(address_string));
}
if (info.username) if (info.username)
json_object_set_new(packed, "username", json_object_set_new(packed, "username",
@ -897,9 +921,13 @@ json_t* pack_connection_event_params(
char id_str[64]; char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info->id); 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}", return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}",
"id", id_str, "id", id_str,
"hostname", info->hostname, "address", have_addr ? address_string : NULL,
"username", info->username, "username", info->username,
"seat", info->seat, "seat", info->seat,
"connection_count", new_connection_count); "connection_count", new_connection_count);
@ -978,3 +1006,15 @@ void ctl_server_event_detached(struct ctl* self)
{ {
ctl_server_enqueue_event(self, EVT_DETACHED, json_object()); 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); 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;
}

View File

@ -57,6 +57,10 @@
#include "util.h" #include "util.h"
#include "option-parser.h" #include "option-parser.h"
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h>
#ifdef ENABLE_PAM #ifdef ENABLE_PAM
#include "pam_auth.h" #include "pam_auth.h"
#endif #endif
@ -87,6 +91,7 @@ struct wayvnc {
struct wl_display* display; struct wl_display* display;
struct wl_registry* registry; struct wl_registry* registry;
struct aml_handler* wl_handler;
struct wl_list outputs; struct wl_list outputs;
struct wl_list seats; struct wl_list seats;
struct cfg cfg; struct cfg cfg;
@ -239,7 +244,10 @@ static void registry_add(void* data, struct wl_registry* registry,
if (!self->is_initializing) { if (!self->is_initializing) {
wl_display_dispatch(self->display); wl_display_dispatch(self->display);
wl_display_roundtrip(self->display); wl_display_roundtrip(self->display);
ctl_server_event_output_added(self->ctl, output->name);
} }
return; return;
} }
@ -321,6 +329,8 @@ static void registry_remove(void* data, struct wl_registry* registry,
} else } else
nvnc_log(NVNC_LOG_INFO, "Output %s went away", out->name); nvnc_log(NVNC_LOG_INFO, "Output %s went away", out->name);
ctl_server_event_output_removed(self->ctl, out->name);
wl_list_remove(&out->link); wl_list_remove(&out->link);
output_destroy(out); output_destroy(out);
@ -392,6 +402,10 @@ static void wayland_detach(struct wayvnc* self)
if (!self->display) if (!self->display)
return; return;
aml_stop(aml_get_default(), self->wl_handler);
aml_unref(self->wl_handler);
self->wl_handler = NULL;
// Screen blanking is required to release wl_shm of linux_dmabuf. // Screen blanking is required to release wl_shm of linux_dmabuf.
if (self->nvnc) if (self->nvnc)
blank_screen(self); blank_screen(self);
@ -464,6 +478,7 @@ static void wayland_detach(struct wayvnc* self)
wl_display_disconnect(self->display); wl_display_disconnect(self->display);
self->display = NULL; self->display = NULL;
if (self->ctl)
ctl_server_event_detached(self->ctl); ctl_server_event_detached(self->ctl);
} }
@ -561,21 +576,24 @@ static int init_wayland(struct wayvnc* self, const char* display)
self->screencopy.on_done = on_capture_done; self->screencopy.on_done = on_capture_done;
self->screencopy.userdata = self; self->screencopy.userdata = self;
struct aml_handler* wl_handler; self->wl_handler = aml_handler_new(wl_display_get_fd(self->display),
wl_handler = aml_handler_new(wl_display_get_fd(self->display),
on_wayland_event, self, NULL); on_wayland_event, self, NULL);
if (!wl_handler) if (!self->wl_handler)
goto failure; goto failure;
int rc = aml_start(aml_get_default(), wl_handler); int rc = aml_start(aml_get_default(), self->wl_handler);
aml_unref(wl_handler);
if (rc < 0) if (rc < 0)
goto failure; goto handler_failure;
return 0; return 0;
failure: failure:
wl_display_disconnect(self->display); wl_display_disconnect(self->display);
self->display = NULL;
handler_failure:
if (self->wl_handler)
aml_unref(self->wl_handler);
self->wl_handler = NULL;
return -1; return -1;
} }
@ -627,17 +645,24 @@ static struct ctl_server_client *client_next(struct ctl* ctl,
(struct ctl_server_client*)nvnc_client_first(self->nvnc); (struct ctl_server_client*)nvnc_client_first(self->nvnc);
} }
static void compose_client_info(const struct wayvnc_client* client,
struct ctl_server_client_info* info)
{
info->id = client->id;
socklen_t addrlen = sizeof(info->address_storage);
nvnc_client_get_address(client->nvnc_client,
(struct sockaddr*)&info->address_storage, &addrlen);
info->username = nvnc_client_get_auth_username(client->nvnc_client);
info->seat = client->seat ? client->seat->name : NULL;
}
static void client_info(const struct ctl_server_client* client_handle, static void client_info(const struct ctl_server_client* client_handle,
struct ctl_server_client_info* info) struct ctl_server_client_info* info)
{ {
const struct nvnc_client *vnc_client = const struct nvnc_client *vnc_client =
(const struct nvnc_client*)client_handle; (const struct nvnc_client*)client_handle;
const struct wayvnc_client *client = nvnc_get_userdata(vnc_client); const struct wayvnc_client *client = nvnc_get_userdata(vnc_client);
compose_client_info(client, info);
info->id = client->id;
info->hostname = nvnc_client_get_hostname(vnc_client);
info->username = nvnc_client_get_auth_username(vnc_client);
info->seat = client->seat ? client->seat->name : NULL;
} }
static int get_output_list(struct ctl* ctl, static int get_output_list(struct ctl* ctl,
@ -728,6 +753,125 @@ static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy); output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy);
pointer_set(&wv_client->pointer, xfx, xfy, button_mask); pointer_set(&wv_client->pointer, xfx, xfy, button_mask);
// This workaround would only work for x11 apps rendered in xwayland.
// It does NOT work for wayland!
// Waylan doesn't have any API to get the cursor image like in x11.
// We would need to grab and parse the surface of the pointer which is a pain.
// Would that even work with hardware cursors?
/*
/
static char ascii_art[] =
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXX "
"XXXXXX "
"XXXXX "
"XXXX "
"XXX "
"XX "
"X ";
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint32_t colour = 0x00ff00ffULL;
#else
uint32_t colour = 0xff00ff00ULL;
#endif
// Print current x11 image
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "Failed to open X display\n");
}
XFixesCursorImage *img = XFixesGetCursorImage(display);
printf("Cursor serial: %ld (%d x %d)\n", img->cursor_serial, img->width, img->height);
//struct nvnc_fb* fb = nvnc_fb_new(32, 32, DRM_FORMAT_RGBA8888, 32);
//assert(fb);
//uint32_t* pixels = nvnc_fb_get_addr(fb);
//for (int i = 0; i < 32 * 32; ++i) {
// pixels[i] = ascii_art[i] != ' ' ? colour : 0;
//}
//nvnc_set_cursor(wv_client->server->nvnc, fb, 32, 32, 0, 0, true);
//nvnc_fb_unref(fb);
*/
/**
* The cursor image itself is returned as a single image at 32 bits per
pixel with 8 bits of alpha in the most significant 8 bits of the
pixel followed by 8 bits each of red, green and finally 8 bits of
blue in the least significant 8 bits. The color components are
pre-multiplied with the alpha component.
Colors are 0xrrggbb
*/
/*
struct nvnc_fb* fb = nvnc_fb_new(img->width, img->height, DRM_FORMAT_RGBA8888, 24);
assert(fb);
// Xlib stores 32-bit data in longs, even if longs are 64-bits long.
unsigned long* argb_data = img->pixels;
//uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
//uint32_t* dst_end = dst + (img->width * img->height);
//while (dst < dst_end) {
// *dst++ = static_cast<uint32_t>(*src++);
//}
uint32_t* pixels = nvnc_fb_get_addr(fb);
for (int i = 0; i < img->width * img->height; ++i) {
// We need to put alpha to the end of hex fo nvnc buffer
uint8_t a = ((argb_data[i] >> 24) & 0xff);
uint8_t r = ((argb_data[i] >> 16) & 0xff);
uint8_t g = ((argb_data[i] >> 8) & 0xff);
uint8_t b = ((argb_data[i] >> 0) & 0xff);
pixels[i] = ((uint32_t)r << 24) | ((uint32_t)g << 16) | ((uint32_t)b << 8) | a;
}
// TODO: use listener!
// CursorNotify
// https://chromium.googlesource.com/external/webrtc/stable/webrtc/+/master/modules/desktop_capture/mouse_cursor_monitor_x11.cc
// https://github.com/zwcloud/XcbSharp/blob/7d012ec64a2f5e6207da708d70856466ab35e173/xfixes.xml#L125
// sudo pacman -S libx11 libxfixes
// g++ file.c -lX11 -lXfixes -o tt
nvnc_set_cursor(wv_client->server->nvnc, fb, img->width, img->height, img->xhot, img->yhot / 2, true);
nvnc_fb_unref(fb);
XFree(img);
XCloseDisplay(display);
*/
} }
static void on_key_event(struct nvnc_client* client, uint32_t symbol, static void on_key_event(struct nvnc_client* client, uint32_t symbol,
@ -739,6 +883,9 @@ static void on_key_event(struct nvnc_client* client, uint32_t symbol,
} }
keyboard_feed(&wv_client->keyboard, symbol, is_pressed); keyboard_feed(&wv_client->keyboard, symbol, is_pressed);
nvnc_client_set_led_state(wv_client->nvnc_client,
keyboard_get_led_state(&wv_client->keyboard));
} }
static void on_key_code_event(struct nvnc_client* client, uint32_t code, static void on_key_code_event(struct nvnc_client* client, uint32_t code,
@ -750,6 +897,9 @@ static void on_key_code_event(struct nvnc_client* client, uint32_t code,
} }
keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed); keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed);
nvnc_client_set_led_state(wv_client->nvnc_client,
keyboard_get_led_state(&wv_client->keyboard));
} }
static void on_client_cut_text(struct nvnc_client* nvnc_client, static void on_client_cut_text(struct nvnc_client* nvnc_client,
@ -1269,14 +1419,13 @@ static void client_destroy(void* obj)
nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d", nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d",
wayvnc->nr_clients); wayvnc->nr_clients);
struct ctl_server_client_info info = { if (wayvnc->ctl) {
.id = self->id, struct ctl_server_client_info info = {};
.hostname = nvnc_client_get_hostname(self->nvnc_client), compose_client_info(self, &info);
.username = nvnc_client_get_auth_username(self->nvnc_client),
.seat = self->seat ? self->seat->name : NULL,
};
ctl_server_event_disconnected(wayvnc->ctl, &info, wayvnc->nr_clients); ctl_server_event_disconnected(wayvnc->ctl, &info,
wayvnc->nr_clients);
}
if (wayvnc->nr_clients == 0 && wayvnc->display) { if (wayvnc->nr_clients == 0 && wayvnc->display) {
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture"); nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
@ -1322,12 +1471,8 @@ static void on_nvnc_client_new(struct nvnc_client* client)
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d", nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
self->nr_clients); self->nr_clients);
struct ctl_server_client_info info = { struct ctl_server_client_info info = {};
.id = wayvnc_client->id, compose_client_info(wayvnc_client, &info);
.hostname = nvnc_client_get_hostname(client),
.username = nvnc_client_get_auth_username(client),
.seat = wayvnc_client->seat ? wayvnc_client->seat->name : NULL,
};
ctl_server_event_connected(self->ctl, &info, self->nr_clients); ctl_server_event_connected(self->ctl, &info, self->nr_clients);
} }
@ -1548,16 +1693,42 @@ void switch_to_prev_output(struct wayvnc* self)
switch_to_output(self, prev); switch_to_output(self, prev);
} }
static char intercepted_error[256];
static void intercept_cmd_error(const struct nvnc_log_data* meta,
const char* message)
{
if (meta->level != NVNC_LOG_ERROR) {
nvnc_default_logger(meta, message);
return;
}
struct nvnc_log_data meta_override = *meta;
meta_override.level = NVNC_LOG_DEBUG;
nvnc_default_logger(&meta_override, message);
size_t len = strlen(intercepted_error);
if (len != 0 && len < sizeof(intercepted_error) - 2)
intercepted_error[len++] = '\n';
strlcpy(intercepted_error + len, message,
sizeof(intercepted_error) - len);
}
static struct cmd_response* on_attach(struct ctl* ctl, const char* display) static struct cmd_response* on_attach(struct ctl* ctl, const char* display)
{ {
struct wayvnc* self = ctl_server_userdata(ctl); struct wayvnc* self = ctl_server_userdata(ctl);
assert(self); assert(self);
// TODO: Add optional output argument memset(intercepted_error, 0, sizeof(intercepted_error));
if (!wayland_attach(self, display, NULL)) nvnc_set_log_fn_thread_local(intercept_cmd_error);
return cmd_failed("Failed to attach to %s", display);
return cmd_ok(); // TODO: Add optional output argument
bool ok = wayland_attach(self, display, NULL);
nvnc_set_log_fn_thread_local(NULL);
return ok ? cmd_ok() : cmd_failed("%s", intercepted_error);
} }
static bool wayland_attach(struct wayvnc* self, const char* display, static bool wayland_attach(struct wayvnc* self, const char* display,
@ -1881,9 +2052,6 @@ int main(int argc, char* argv[])
else if (use_websocket) else if (use_websocket)
socket_type = SOCKET_TYPE_WEBSOCKET; socket_type = SOCKET_TYPE_WEBSOCKET;
if (init_nvnc(&self, address, port, socket_type) < 0)
goto nvnc_failure;
if (!start_detached) { if (!start_detached) {
if (self.screencopy.manager) if (self.screencopy.manager)
screencopy_init(&self.screencopy); screencopy_init(&self.screencopy);
@ -1916,6 +2084,9 @@ int main(int argc, char* argv[])
if (!self.ctl) if (!self.ctl)
goto ctl_server_failure; goto ctl_server_failure;
if (init_nvnc(&self, address, port, socket_type) < 0)
goto nvnc_failure;
if (self.display) if (self.display)
wl_display_dispatch_pending(self.display); wl_display_dispatch_pending(self.display);
@ -1933,6 +2104,7 @@ int main(int argc, char* argv[])
screencopy_stop(&self.screencopy); screencopy_stop(&self.screencopy);
ctl_server_destroy(self.ctl); ctl_server_destroy(self.ctl);
self.ctl = NULL;
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
@ -1948,11 +2120,10 @@ int main(int argc, char* argv[])
return 0; return 0;
nvnc_failure:
ctl_server_destroy(self.ctl);
ctl_server_failure: ctl_server_failure:
capture_failure: capture_failure:
nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc);
nvnc_failure:
wayland_failure: wayland_failure:
aml_unref(aml); aml_unref(aml);
failure: failure:

View File

@ -211,7 +211,18 @@ void wlr_output_manager_destroy(void)
if (!wlr_output_manager) if (!wlr_output_manager)
return; 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); 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, bool wlr_output_manager_resize_output(struct output* 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(sizeof(*response), num_msg); struct pam_response* resp = calloc(num_msg, sizeof(*resp));
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

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

View File

@ -269,8 +269,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.
*hostname=...* *address=...*
The hostname or IP of this client. May be null. The IP address 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,8 +288,8 @@ 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.
*hostname=...* *address=...*
The hostname or IP of this client. May be null. The IP address 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.

View File

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