Compare commits

...

6 Commits

Author SHA1 Message Date
Andri Yngvason 7c3b606b8d Detach instead of exiting if all outputs go away 2023-11-15 20:55:58 +00:00
Andri Yngvason 13043987e5 builds: archlinux: Add python cryptodomex
This is needed by vncdo
2023-11-14 21:19:29 +00:00
Andri Yngvason f9bacc8eb8 examples: Add script to auto attach to any compositor 2023-11-14 20:56:36 +00:00
Andri Yngvason 2c524e28fc Emit "detached" control event 2023-11-14 20:49:07 +00:00
Andri Yngvason 861e140205 Detach instead of exiting when compositor goes away 2023-11-14 20:49:00 +00:00
Andri Yngvason 0938162e6f Implement detached mode
This makes it possible to attach to a compositor at a later time and to
detach and re-attach.
2023-11-14 20:47:54 +00:00
9 changed files with 482 additions and 114 deletions

View File

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

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

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022 Jim Ramsay * Copyright (c) 2022 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -41,6 +42,8 @@ struct ctl_server_output {
struct ctl_server_actions { struct ctl_server_actions {
void* userdata; void* userdata;
struct cmd_response* (*on_attach)(struct ctl*, const char* display);
struct cmd_response* (*on_detach)(struct ctl*);
struct cmd_response* (*on_output_cycle)(struct ctl*, struct cmd_response* (*on_output_cycle)(struct ctl*,
enum output_cycle_direction direction); enum output_cycle_direction direction);
struct cmd_response* (*on_output_switch)(struct ctl*, struct cmd_response* (*on_output_switch)(struct ctl*,
@ -79,3 +82,5 @@ void ctl_server_event_disconnected(struct ctl*,
void ctl_server_event_capture_changed(struct ctl*, void ctl_server_event_capture_changed(struct ctl*,
const char* captured_output); const char* captured_output);
void ctl_server_event_detached(struct ctl*);

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -403,6 +404,8 @@ static void pretty_print(json_t* data,
case CMD_OUTPUT_LIST: case CMD_OUTPUT_LIST:
pretty_output_list(data); pretty_output_list(data);
break; break;
case CMD_ATTACH:
case CMD_DETACH:
case CMD_CLIENT_DISCONNECT: case CMD_CLIENT_DISCONNECT:
case CMD_OUTPUT_SET: case CMD_OUTPUT_SET:
case CMD_OUTPUT_CYCLE: case CMD_OUTPUT_CYCLE:

View File

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

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022-2023 Jim Ramsay * Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -46,6 +47,11 @@ struct cmd {
enum cmd_type type; enum cmd_type type;
}; };
struct cmd_attach {
struct cmd cmd;
char display[128];
};
struct cmd_help { struct cmd_help {
struct cmd cmd; struct cmd cmd;
char id[64]; char id[64];
@ -105,6 +111,19 @@ static void cmd_response_destroy(struct cmd_response* self)
free(self); free(self);
} }
static struct cmd_attach* cmd_attach_new(json_t* args,
struct jsonipc_error* err)
{
const char* display = NULL;
if (json_unpack(args, "{s:s}", "display", &display) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing display name");
return NULL;
}
struct cmd_attach* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->display, display, sizeof(cmd->display));
return cmd;
}
static struct cmd_help* cmd_help_new(json_t* args, static struct cmd_help* cmd_help_new(json_t* args,
struct jsonipc_error* err) struct jsonipc_error* err)
{ {
@ -185,6 +204,9 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
enum cmd_type cmd_type = ctl_command_parse_name(ipc->method); enum cmd_type cmd_type = ctl_command_parse_name(ipc->method);
struct cmd* cmd = NULL; struct cmd* cmd = NULL;
switch (cmd_type) { switch (cmd_type) {
case CMD_ATTACH:
cmd = (struct cmd*)cmd_attach_new(ipc->params, err);
break;
case CMD_HELP: case CMD_HELP:
cmd = (struct cmd*)cmd_help_new(ipc->params, err); cmd = (struct cmd*)cmd_help_new(ipc->params, err);
break; break;
@ -194,6 +216,7 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
case CMD_CLIENT_DISCONNECT: case CMD_CLIENT_DISCONNECT:
cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err); cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err);
break; break;
case CMD_DETACH:
case CMD_VERSION: case CMD_VERSION:
case CMD_EVENT_RECEIVE: case CMD_EVENT_RECEIVE:
case CMD_CLIENT_LIST: case CMD_CLIENT_LIST:
@ -407,6 +430,11 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name); nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name);
struct cmd_response* response = NULL; struct cmd_response* response = NULL;
switch (cmd->type) { switch (cmd->type) {
case CMD_ATTACH:{
struct cmd_attach* c = (struct cmd_attach*)cmd;
response = self->actions.on_attach(self, c->display);
break;
}
case CMD_HELP:{ case CMD_HELP:{
struct cmd_help* c = (struct cmd_help*)cmd; struct cmd_help* c = (struct cmd_help*)cmd;
response = generate_help_object(c->id, c->id_is_command); response = generate_help_object(c->id, c->id_is_command);
@ -423,6 +451,9 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
response = self->actions.on_disconnect_client(self, c->id); response = self->actions.on_disconnect_client(self, c->id);
break; break;
} }
case CMD_DETACH:
response = self->actions.on_detach(self);
break;
case CMD_WAYVNC_EXIT: case CMD_WAYVNC_EXIT:
response = self->actions.on_wayvnc_exit(self); response = self->actions.on_wayvnc_exit(self);
break; break;
@ -942,3 +973,8 @@ void ctl_server_event_capture_changed(struct ctl* self,
ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED, ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED,
json_pack("{s:s}", "output", captured_output)); json_pack("{s:s}", "output", captured_output));
} }
void ctl_server_event_detached(struct ctl* self)
{
ctl_server_enqueue_event(self, EVT_DETACHED, json_object());
}

View File

@ -119,6 +119,8 @@ struct wayvnc {
struct ctl* ctl; struct ctl* ctl;
bool is_initializing; bool is_initializing;
bool start_detached;
}; };
struct wayvnc_client { struct wayvnc_client {
@ -144,6 +146,11 @@ static void client_init_seat(struct wayvnc_client* self);
static void client_init_pointer(struct wayvnc_client* self); static void client_init_pointer(struct wayvnc_client* self);
static void client_init_keyboard(struct wayvnc_client* self); static void client_init_keyboard(struct wayvnc_client* self);
static void client_init_data_control(struct wayvnc_client* self); static void client_init_data_control(struct wayvnc_client* self);
static void client_detach_wayland(struct wayvnc_client* self);
static int blank_screen(struct wayvnc* self);
static bool wayland_attach(struct wayvnc* self, const char* display,
const char* output);
static void wayland_detach(struct wayvnc* self);
struct wl_shm* wl_shm = NULL; struct wl_shm* wl_shm = NULL;
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf = NULL; struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf = NULL;
@ -303,8 +310,13 @@ static void registry_remove(void* data, struct wl_registry* registry,
output_destroy(out); output_destroy(out);
if (out == self->selected_output) { if (out == self->selected_output) {
nvnc_log(NVNC_LOG_ERROR, "No fallback outputs left. Exiting..."); if (self->start_detached) {
wayvnc_exit(self); nvnc_log(NVNC_LOG_WARNING, "No fallback outputs left. Detaching...");
wayland_detach(self);
} else {
nvnc_log(NVNC_LOG_ERROR, "No fallback outputs left. Exiting...");
wayvnc_exit(self);
}
} }
return; return;
@ -360,44 +372,118 @@ static int init_render_node(int* fd)
} }
#endif #endif
void wayvnc_destroy(struct wayvnc* self) static void wayland_detach(struct wayvnc* self)
{ {
cfg_destroy(&self->cfg); if (!self->display)
return;
// Screen blanking is required to release wl_shm of linux_dmabuf.
if (self->nvnc)
blank_screen(self);
if (self->nvnc) {
struct nvnc_client* nvnc_client;
for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client =
nvnc_get_userdata(nvnc_client);
client_detach_wayland(client);
}
}
self->selected_output = NULL;
self->screencopy.wl_output = NULL;
output_list_destroy(&self->outputs); output_list_destroy(&self->outputs);
seat_list_destroy(&self->seats); seat_list_destroy(&self->seats);
if (zwp_linux_dmabuf)
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf);
zwp_linux_dmabuf = NULL;
if (self->screencopy.manager) {
screencopy_stop(&self->screencopy);
screencopy_destroy(&self->screencopy);
zwlr_screencopy_manager_v1_destroy(self->screencopy.manager);
}
self->screencopy.manager = NULL;
if (xdg_output_manager) if (xdg_output_manager)
zxdg_output_manager_v1_destroy(xdg_output_manager); zxdg_output_manager_v1_destroy(xdg_output_manager);
xdg_output_manager = NULL;
if (wlr_output_power_manager) if (wlr_output_power_manager)
zwlr_output_power_manager_v1_destroy(wlr_output_power_manager); zwlr_output_power_manager_v1_destroy(wlr_output_power_manager);
wlr_output_power_manager = NULL;
wl_shm_destroy(wl_shm); wl_shm_destroy(wl_shm);
wl_shm = NULL;
if (self->keyboard_manager) if (self->keyboard_manager)
zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager); zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager);
self->keyboard_manager = NULL;
if (self->pointer_manager) if (self->pointer_manager)
zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager); zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager);
self->pointer_manager = NULL;
if (self->data_control_manager) if (self->data_control_manager)
zwlr_data_control_manager_v1_destroy(self->data_control_manager); zwlr_data_control_manager_v1_destroy(self->data_control_manager);
self->data_control_manager = NULL;
if (self->screencopy.manager) if (self->performance_ticker) {
zwlr_screencopy_manager_v1_destroy(self->screencopy.manager); aml_stop(aml_get_default(), self->performance_ticker);
if (self->performance_ticker)
aml_unref(self->performance_ticker); aml_unref(self->performance_ticker);
}
self->performance_ticker = NULL;
if (self->capture_retry_timer) if (self->capture_retry_timer)
aml_unref(self->capture_retry_timer); aml_unref(self->capture_retry_timer);
self->capture_retry_timer = NULL;
wl_registry_destroy(self->registry); wl_registry_destroy(self->registry);
self->registry = NULL;
wl_display_disconnect(self->display); wl_display_disconnect(self->display);
self->display = NULL;
ctl_server_event_detached(self->ctl);
} }
static int init_wayland(struct wayvnc* self) void wayvnc_destroy(struct wayvnc* self)
{
cfg_destroy(&self->cfg);
wayland_detach(self);
}
void on_wayland_event(void* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
int rc MAYBE_UNUSED = wl_display_prepare_read(self->display);
assert(rc == 0);
if (wl_display_read_events(self->display) < 0) {
if (errno == EPIPE || errno == ECONNRESET) {
nvnc_log(NVNC_LOG_ERROR, "Compositor has gone away. Exiting...");
if (self->start_detached)
wayland_detach(self);
else
wayvnc_exit(self);
return;
} else {
nvnc_log(NVNC_LOG_ERROR, "Failed to read wayland events: %m");
}
}
if (wl_display_dispatch_pending(self->display) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to dispatch pending");
wayland_detach(self);
// TODO: Re-attach
}
}
static int init_wayland(struct wayvnc* self, const char* display)
{ {
self->is_initializing = true; self->is_initializing = true;
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {
@ -405,9 +491,10 @@ static int init_wayland(struct wayvnc* self)
.global_remove = registry_remove, .global_remove = registry_remove,
}; };
self->display = wl_display_connect(NULL); self->display = wl_display_connect(display);
if (!self->display) { if (!self->display) {
const char* display_name = getenv("WAYLAND_DISPLAY"); const char* display_name = display ? display:
getenv("WAYLAND_DISPLAY");
if (!display_name) { if (!display_name) {
nvnc_log(NVNC_LOG_ERROR, "WAYLAND_DISPLAY is not set in the environment"); nvnc_log(NVNC_LOG_ERROR, "WAYLAND_DISPLAY is not set in the environment");
} else { } else {
@ -457,6 +544,17 @@ static int init_wayland(struct wayvnc* self)
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;
wl_handler = aml_handler_new(wl_display_get_fd(self->display),
on_wayland_event, self, NULL);
if (!wl_handler)
goto failure;
int rc = aml_start(aml_get_default(), wl_handler);
aml_unref(wl_handler);
if (rc < 0)
goto failure;
return 0; return 0;
failure: failure:
@ -464,26 +562,6 @@ failure:
return -1; return -1;
} }
void on_wayland_event(void* obj)
{
struct wayvnc* self = aml_get_userdata(obj);
int rc MAYBE_UNUSED = wl_display_prepare_read(self->display);
assert(rc == 0);
if (wl_display_read_events(self->display) < 0) {
if (errno == EPIPE || errno == ECONNRESET) {
nvnc_log(NVNC_LOG_ERROR, "Compositor has gone away. Exiting...");
wayvnc_exit(self);
} else {
nvnc_log(NVNC_LOG_ERROR, "Failed to read wayland events: %m");
}
}
if (wl_display_dispatch_pending(self->display) < 0)
nvnc_log(NVNC_LOG_ERROR, "Failed to dispatch pending");
}
void wayvnc_exit(struct wayvnc* self) void wayvnc_exit(struct wayvnc* self)
{ {
self->do_exit = true; self->do_exit = true;
@ -606,23 +684,12 @@ int init_main_loop(struct wayvnc* self)
{ {
struct aml* loop = aml_get_default(); struct aml* loop = aml_get_default();
struct aml_handler* wl_handler;
wl_handler = aml_handler_new(wl_display_get_fd(self->display),
on_wayland_event, self, NULL);
if (!wl_handler)
return -1;
int rc = aml_start(loop, wl_handler);
aml_unref(wl_handler);
if (rc < 0)
return -1;
struct aml_signal* sig; struct aml_signal* sig;
sig = aml_signal_new(SIGINT, on_signal, self, NULL); sig = aml_signal_new(SIGINT, on_signal, self, NULL);
if (!sig) if (!sig)
return -1; return -1;
rc = aml_start(loop, sig); int rc = aml_start(loop, sig);
aml_unref(sig); aml_unref(sig);
if (rc < 0) if (rc < 0)
return -1; return -1;
@ -636,6 +703,10 @@ static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
struct wayvnc_client* wv_client = nvnc_get_userdata(client); struct wayvnc_client* wv_client = nvnc_get_userdata(client);
struct wayvnc* wayvnc = wv_client->server; struct wayvnc* wayvnc = wv_client->server;
if (!wv_client->pointer.pointer) {
return;
}
uint32_t xfx = 0, xfy = 0; uint32_t xfx = 0, xfy = 0;
output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy); output_transform_coord(wayvnc->selected_output, x, y, &xfx, &xfy);
@ -646,6 +717,9 @@ static void on_key_event(struct nvnc_client* client, uint32_t symbol,
bool is_pressed) bool is_pressed)
{ {
struct wayvnc_client* wv_client = nvnc_get_userdata(client); struct wayvnc_client* wv_client = nvnc_get_userdata(client);
if (!wv_client->keyboard.virtual_keyboard) {
return;
}
keyboard_feed(&wv_client->keyboard, symbol, is_pressed); keyboard_feed(&wv_client->keyboard, symbol, is_pressed);
} }
@ -654,6 +728,9 @@ static void on_key_code_event(struct nvnc_client* client, uint32_t code,
bool is_pressed) bool is_pressed)
{ {
struct wayvnc_client* wv_client = nvnc_get_userdata(client); struct wayvnc_client* wv_client = nvnc_get_userdata(client);
if (!wv_client->keyboard.virtual_keyboard) {
return;
}
keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed); keyboard_feed_code(&wv_client->keyboard, code + 8, is_pressed);
} }
@ -701,9 +778,15 @@ static struct nvnc_fb* create_placeholder_buffer(uint16_t width, uint16_t height
static int blank_screen(struct wayvnc* self) static int blank_screen(struct wayvnc* self)
{ {
struct nvnc_fb* placeholder_fb = int width = 1280;
create_placeholder_buffer(self->selected_output->width, int height = 720;
self->selected_output->height);
if (self->selected_output) {
width = output_get_transformed_width(self->selected_output);
height = output_get_transformed_height(self->selected_output);
}
struct nvnc_fb* placeholder_fb = create_placeholder_buffer(width, height);
if (!placeholder_fb) { if (!placeholder_fb) {
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate a placeholder buffer"); nvnc_log(NVNC_LOG_ERROR, "Failed to allocate a placeholder buffer");
return -1; return -1;
@ -807,13 +890,10 @@ static int init_nvnc(struct wayvnc* self, const char* addr, uint16_t port,
} }
} }
if (self->pointer_manager) nvnc_set_pointer_fn(self->nvnc, on_pointer_event);
nvnc_set_pointer_fn(self->nvnc, on_pointer_event);
if (self->keyboard_manager) { nvnc_set_key_fn(self->nvnc, on_key_event);
nvnc_set_key_fn(self->nvnc, on_key_event); nvnc_set_key_code_fn(self->nvnc, on_key_code_event);
nvnc_set_key_code_fn(self->nvnc, on_key_code_event);
}
nvnc_set_new_client_fn(self->nvnc, on_nvnc_client_new); nvnc_set_new_client_fn(self->nvnc, on_nvnc_client_new);
nvnc_set_cut_text_fn(self->nvnc, on_client_cut_text); nvnc_set_cut_text_fn(self->nvnc, on_client_cut_text);
@ -1076,6 +1156,34 @@ static void stop_performance_ticker(struct wayvnc* self)
aml_stop(aml_get_default(), self->performance_ticker); aml_stop(aml_get_default(), self->performance_ticker);
} }
static void client_init_wayland(struct wayvnc_client* self)
{
client_init_seat(self);
client_init_keyboard(self);
client_init_pointer(self);
client_init_data_control(self);
}
static void client_detach_wayland(struct wayvnc_client* self)
{
self->seat = NULL;
if (self->keyboard.virtual_keyboard) {
zwp_virtual_keyboard_v1_destroy(
self->keyboard.virtual_keyboard);
keyboard_destroy(&self->keyboard);
}
self->keyboard.virtual_keyboard = NULL;
if (self->pointer.pointer)
pointer_destroy(&self->pointer);
self->pointer.pointer = NULL;
if (self->data_control.manager)
data_control_destroy(&self->data_control);
self->data_control.manager = NULL;
}
static unsigned next_client_id = 1; static unsigned next_client_id = 1;
static struct wayvnc_client* client_create(struct wayvnc* wayvnc, static struct wayvnc_client* client_create(struct wayvnc* wayvnc,
@ -1089,10 +1197,10 @@ static struct wayvnc_client* client_create(struct wayvnc* wayvnc,
self->nvnc_client = nvnc_client; self->nvnc_client = nvnc_client;
self->id = next_client_id++; self->id = next_client_id++;
client_init_seat(self);
client_init_keyboard(self); if (wayvnc->display) {
client_init_pointer(self); client_init_wayland(self);
client_init_data_control(self); }
return self; return self;
} }
@ -1122,7 +1230,7 @@ static void client_destroy(void* obj)
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) { if (wayvnc->nr_clients == 0 && wayvnc->display) {
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture"); nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
screencopy_stop(&wayvnc->screencopy); screencopy_stop(&wayvnc->screencopy);
stop_performance_ticker(wayvnc); stop_performance_ticker(wayvnc);
@ -1143,6 +1251,13 @@ static void client_destroy(void* obj)
free(self); free(self);
} }
static void handle_first_client(struct wayvnc* self)
{
nvnc_log(NVNC_LOG_INFO, "Starting screen capture");
start_performance_ticker(self);
wayvnc_start_capture_immediate(self);
}
static void on_nvnc_client_new(struct nvnc_client* client) static void on_nvnc_client_new(struct nvnc_client* client)
{ {
struct nvnc* nvnc = nvnc_client_get_server(client); struct nvnc* nvnc = nvnc_client_get_server(client);
@ -1152,10 +1267,8 @@ static void on_nvnc_client_new(struct nvnc_client* client)
assert(wayvnc_client); assert(wayvnc_client);
nvnc_set_userdata(client, wayvnc_client, client_destroy); nvnc_set_userdata(client, wayvnc_client, client_destroy);
if (self->nr_clients == 0) { if (self->nr_clients == 0 && self->display) {
nvnc_log(NVNC_LOG_INFO, "Starting screen capture"); handle_first_client(self);
start_performance_ticker(self);
wayvnc_start_capture_immediate(self);
} }
self->nr_clients++; self->nr_clients++;
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d", nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
@ -1387,6 +1500,87 @@ void switch_to_prev_output(struct wayvnc* self)
switch_to_output(self, prev); switch_to_output(self, prev);
} }
static struct cmd_response* on_attach(struct ctl* ctl, const char* display)
{
struct wayvnc* self = ctl_server_userdata(ctl);
assert(self);
// TODO: Add optional output argument
if (!wayland_attach(self, display, NULL))
return cmd_failed("Failed to attach to %s", display);
return cmd_ok();
}
static bool wayland_attach(struct wayvnc* self, const char* display,
const char* output)
{
if (self->display) {
wayland_detach(self);
}
nvnc_log(NVNC_LOG_DEBUG, "Attaching to %s", display);
if (init_wayland(self, display) < 0) {
return false;
}
struct output* out;
if (output) {
out = output_find_by_name(&self->outputs, output);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output: %s", output);
wayland_detach(self);
return false;
}
} else {
out = output_first(&self->outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output available");
wayland_detach(self);
return false;
}
}
screencopy_init(&self->screencopy);
if (!self->screencopy.manager) {
nvnc_log(NVNC_LOG_ERROR, "Attached display does not implement wlr-screencopy-v1");
wayland_detach(self);
return false;
}
set_selected_output(self, out);
struct nvnc_client* nvnc_client;
for (nvnc_client = nvnc_client_first(self->nvnc); nvnc_client;
nvnc_client = nvnc_client_next(nvnc_client)) {
struct wayvnc_client* client = nvnc_get_userdata(nvnc_client);
client_init_wayland(client);
}
nvnc_log(NVNC_LOG_INFO, "Attached to %s", display);
if (self->nr_clients > 0) {
handle_first_client(self);
}
return true;
}
static struct cmd_response* on_detach(struct ctl* ctl)
{
struct wayvnc* self = ctl_server_userdata(ctl);
assert(self);
if (!self->display) {
return cmd_failed("Not attached!");
}
wayland_detach(self);
nvnc_log(NVNC_LOG_INFO, "Detached from wayland server");
return cmd_ok();
}
static int log_level_from_string(const char* str) static int log_level_from_string(const char* str)
{ {
if (0 == strcmp(str, "quiet")) return NVNC_LOG_PANIC; if (0 == strcmp(str, "quiet")) return NVNC_LOG_PANIC;
@ -1417,6 +1611,7 @@ int main(int argc, char* argv[])
int port = 0; int port = 0;
bool use_unix_socket = false; bool use_unix_socket = false;
bool use_websocket = false; bool use_websocket = false;
bool start_detached = false;
const char* output_name = NULL; const char* output_name = NULL;
const char* seat_name = NULL; const char* seat_name = NULL;
@ -1465,6 +1660,8 @@ int main(int argc, char* argv[])
"Create unix domain socket." }, "Create unix domain socket." },
{ 'd', "disable-input", NULL, { 'd', "disable-input", NULL,
"Disable all remote input." }, "Disable all remote input." },
{ 'D', "detached", NULL,
"Start detached from a compositor." },
{ 'V', "version", NULL, { 'V', "version", NULL,
"Show version info." }, "Show version info." },
{ 'v', "verbose", NULL, { 'v', "verbose", NULL,
@ -1511,6 +1708,9 @@ int main(int argc, char* argv[])
max_rate = atoi(option_parser_get_value(&option_parser, "max-fps")); max_rate = atoi(option_parser_get_value(&option_parser, "max-fps"));
use_transient_seat = !!option_parser_get_value(&option_parser, use_transient_seat = !!option_parser_get_value(&option_parser,
"transient-seat"); "transient-seat");
start_detached = !!option_parser_get_value(&option_parser, "detached");
self.start_detached = start_detached;
keyboard_options = option_parser_get_value(&option_parser, "keyboard"); keyboard_options = option_parser_get_value(&option_parser, "keyboard");
if (keyboard_options) if (keyboard_options)
@ -1572,37 +1772,49 @@ int main(int argc, char* argv[])
self.disable_input = disable_input; self.disable_input = disable_input;
self.use_transient_seat = use_transient_seat; self.use_transient_seat = use_transient_seat;
if (init_wayland(&self) < 0) { struct aml* aml = aml_new();
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise wayland"); if (!aml)
return 1; goto failure;
aml_set_default(aml);
if (init_main_loop(&self) < 0)
goto failure;
if (!start_detached) {
if (init_wayland(&self, NULL) < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to initialise wayland");
goto wayland_failure;
}
struct output* out;
if (output_name) {
out = output_find_by_name(&self.outputs, output_name);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output");
goto wayland_failure;
}
} else {
out = output_first(&self.outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output found");
goto wayland_failure;
}
}
set_selected_output(&self, out);
struct seat* seat = NULL;
if (seat_name) {
seat = seat_find_by_name(&self.seats, seat_name);
if (!seat) {
nvnc_log(NVNC_LOG_ERROR, "No such seat");
goto wayland_failure;
}
}
self.selected_seat = seat;
} }
struct output* out;
if (output_name) {
out = output_find_by_name(&self.outputs, output_name);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No such output");
goto failure;
}
} else {
out = output_first(&self.outputs);
if (!out) {
nvnc_log(NVNC_LOG_ERROR, "No output found");
goto failure;
}
}
set_selected_output(&self, out);
struct seat* seat = NULL;
if (seat_name) {
seat = seat_find_by_name(&self.seats, seat_name);
if (!seat) {
nvnc_log(NVNC_LOG_ERROR, "No such seat");
goto failure;
}
}
self.selected_seat = seat;
self.screencopy.rate_limit = max_rate; self.screencopy.rate_limit = max_rate;
self.screencopy.enable_linux_dmabuf = enable_gpu_features; self.screencopy.enable_linux_dmabuf = enable_gpu_features;
@ -1615,15 +1827,6 @@ int main(int argc, char* argv[])
if (aml_unstable_abi_version != AML_UNSTABLE_API) if (aml_unstable_abi_version != AML_UNSTABLE_API)
nvnc_log(NVNC_LOG_PANIC, "libaml is incompatible with this build of wayvnc!"); nvnc_log(NVNC_LOG_PANIC, "libaml is incompatible with this build of wayvnc!");
struct aml* aml = aml_new();
if (!aml)
goto main_loop_failure;
aml_set_default(aml);
if (init_main_loop(&self) < 0)
goto main_loop_failure;
enum socket_type socket_type = SOCKET_TYPE_TCP; enum socket_type socket_type = SOCKET_TYPE_TCP;
if (use_unix_socket) if (use_unix_socket)
socket_type = SOCKET_TYPE_UNIX; socket_type = SOCKET_TYPE_UNIX;
@ -1633,12 +1836,14 @@ int main(int argc, char* argv[])
if (init_nvnc(&self, address, port, socket_type) < 0) if (init_nvnc(&self, address, port, socket_type) < 0)
goto nvnc_failure; goto nvnc_failure;
if (self.screencopy.manager) if (!start_detached) {
screencopy_init(&self.screencopy); if (self.screencopy.manager)
screencopy_init(&self.screencopy);
if (!self.screencopy.manager) { if (!self.screencopy.manager) {
nvnc_log(NVNC_LOG_ERROR, "screencopy is not supported by compositor"); nvnc_log(NVNC_LOG_ERROR, "screencopy is not supported by compositor");
goto capture_failure; goto capture_failure;
}
} }
self.screencopy.overlay_cursor = overlay_cursor; self.screencopy.overlay_cursor = overlay_cursor;
@ -1649,6 +1854,8 @@ int main(int argc, char* argv[])
const struct ctl_server_actions ctl_actions = { const struct ctl_server_actions ctl_actions = {
.userdata = &self, .userdata = &self,
.on_attach = on_attach,
.on_detach = on_detach,
.on_output_cycle = on_output_cycle, .on_output_cycle = on_output_cycle,
.on_output_switch = on_output_switch, .on_output_switch = on_output_switch,
.client_next = client_next, .client_next = client_next,
@ -1661,32 +1868,34 @@ int main(int argc, char* argv[])
if (!self.ctl) if (!self.ctl)
goto ctl_server_failure; goto ctl_server_failure;
wl_display_dispatch_pending(self.display); if (self.display)
wl_display_dispatch_pending(self.display);
while (!self.do_exit) { while (!self.do_exit) {
wl_display_flush(self.display); if (self.display)
wl_display_flush(self.display);
aml_poll(aml, -1); aml_poll(aml, -1);
aml_dispatch(aml); aml_dispatch(aml);
} }
nvnc_log(NVNC_LOG_INFO, "Exiting..."); nvnc_log(NVNC_LOG_INFO, "Exiting...");
screencopy_stop(&self.screencopy); if (self.display)
screencopy_stop(&self.screencopy);
ctl_server_destroy(self.ctl); ctl_server_destroy(self.ctl);
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
if (zwp_linux_dmabuf) self.nvnc = NULL;
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf); wayvnc_destroy(&self);
if (self.screencopy.manager)
screencopy_destroy(&self.screencopy);
#ifdef ENABLE_SCREENCOPY_DMABUF #ifdef ENABLE_SCREENCOPY_DMABUF
if (gbm_device) { if (gbm_device) {
gbm_device_destroy(gbm_device); gbm_device_destroy(gbm_device);
close(drm_fd); close(drm_fd);
} }
#endif #endif
wayvnc_destroy(&self);
aml_unref(aml); aml_unref(aml);
return 0; return 0;
@ -1696,15 +1905,16 @@ capture_failure:
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
nvnc_failure: nvnc_failure:
wayland_failure:
aml_unref(aml); aml_unref(aml);
main_loop_failure:
failure: failure:
self.nvnc = NULL;
wayvnc_destroy(&self);
#ifdef ENABLE_SCREENCOPY_DMABUF #ifdef ENABLE_SCREENCOPY_DMABUF
if (gbm_device) if (gbm_device)
gbm_device_destroy(gbm_device); gbm_device_destroy(gbm_device);
if (drm_fd >= 0) if (drm_fd >= 0)
close(drm_fd); close(drm_fd);
#endif #endif
wayvnc_destroy(&self);
return 1; return 1;
} }

View File

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