diff --git a/include/common.h b/include/common.h index 50ed1b0..cb3bb30 100644 --- a/include/common.h +++ b/include/common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 - 2020 Andri Yngvason + * Copyright (c) 2019 - 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 @@ -109,6 +109,8 @@ struct nvnc_client { uint32_t cursor_seq; int quality; bool formats_changed; + enum nvnc_keyboard_led_state led_state; + enum nvnc_keyboard_led_state pending_led_state; #ifdef HAVE_CRYPTO struct crypto_key* apple_dh_secret; diff --git a/include/neatvnc.h b/include/neatvnc.h index 806531a..f29504d 100644 --- a/include/neatvnc.h +++ b/include/neatvnc.h @@ -86,6 +86,12 @@ enum nvnc_transform { NVNC_TRANSFORM_FLIPPED_270 = 7, }; +enum nvnc_keyboard_led_state { + NVNC_KEYBOARD_LED_SCROLL_LOCK = 1 << 0, + NVNC_KEYBOARD_LED_NUM_LOCK = 1 << 1, + NVNC_KEYBOARD_LED_CAPS_LOCK = 1 << 2, +}; + enum nvnc_log_level { NVNC_LOG_PANIC = 0, NVNC_LOG_ERROR = 1, @@ -150,6 +156,9 @@ struct nvnc_client* nvnc_client_first(struct nvnc* self); struct nvnc_client* nvnc_client_next(struct nvnc_client* client); void nvnc_client_close(struct nvnc_client* client); +void nvnc_client_set_led_state(struct nvnc_client*, + enum nvnc_keyboard_led_state); + void nvnc_set_name(struct nvnc* self, const char* name); void nvnc_set_key_fn(struct nvnc* self, nvnc_key_fn); diff --git a/include/rfb-proto.h b/include/rfb-proto.h index 5b8f1fb..65e12b2 100644 --- a/include/rfb-proto.h +++ b/include/rfb-proto.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 - 2022 Andri Yngvason + * Copyright (c) 2019 - 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 @@ -69,9 +69,11 @@ enum rfb_encodings { RFB_ENCODING_CURSOR = -239, RFB_ENCODING_DESKTOPSIZE = -223, RFB_ENCODING_QEMU_EXT_KEY_EVENT = -258, + RFB_ENCODING_QEMU_LED_STATE = -261, RFB_ENCODING_EXTENDEDDESKTOPSIZE = -308, RFB_ENCODING_PTS = -1000, RFB_ENCODING_NTP = -1001, + RFB_ENCODING_VMWARE_LED_STATE = 0x574d5668, }; #define RFB_ENCODING_JPEG_HIGHQ -23 @@ -114,6 +116,13 @@ enum rfb_rsa_aes_cred_subtype { RFB_RSA_AES_CRED_SUBTYPE_ONLY_PASS = 2, }; +// This is the same for both qemu and vmware extensions +enum rfb_led_state { + RFB_LED_STATE_SCROLL_LOCK = 1 << 0, + RFB_LED_STATE_NUM_LOCK = 1 << 1, + RFB_LED_STATE_CAPS_LOCK = 1 << 2, +}; + struct rfb_security_types_msg { uint8_t n; uint8_t types[0]; diff --git a/src/server.c b/src/server.c index e6647f8..cb59674 100644 --- a/src/server.c +++ b/src/server.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 - 2022 Andri Yngvason + * Copyright (c) 2019 - 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 @@ -85,6 +85,7 @@ static void process_fb_update_requests(struct nvnc_client* client); static void sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr); static const char* encoding_to_string(enum rfb_encodings encoding); +static bool client_send_led_state(struct nvnc_client* client); #if defined(PROJECT_VERSION) EXPORT const char nvnc_version[] = PROJECT_VERSION; @@ -1025,6 +1026,8 @@ static int on_client_set_encodings(struct nvnc_client* client) case RFB_ENCODING_DESKTOPSIZE: case RFB_ENCODING_EXTENDEDDESKTOPSIZE: case RFB_ENCODING_QEMU_EXT_KEY_EVENT: + case RFB_ENCODING_QEMU_LED_STATE: + case RFB_ENCODING_VMWARE_LED_STATE: #ifdef ENABLE_EXPERIMENTAL case RFB_ENCODING_PTS: case RFB_ENCODING_NTP: @@ -1117,6 +1120,8 @@ static const char* encoding_to_string(enum rfb_encodings encoding) case RFB_ENCODING_DESKTOPSIZE: return "desktop-size"; case RFB_ENCODING_EXTENDEDDESKTOPSIZE: return "extended-desktop-size"; case RFB_ENCODING_QEMU_EXT_KEY_EVENT: return "qemu-extended-key-event"; + case RFB_ENCODING_QEMU_LED_STATE: return "qemu-led-state"; + case RFB_ENCODING_VMWARE_LED_STATE: return "vmware-led-state"; case RFB_ENCODING_PTS: return "pts"; case RFB_ENCODING_NTP: return "ntp"; } @@ -1225,6 +1230,11 @@ static void process_fb_update_requests(struct nvnc_client* client) return; } + if (client_send_led_state(client)) { + if (--client->n_pending_requests <= 0) + return; + } + if (!pixman_region_not_empty(&client->damage)) return; @@ -1796,6 +1806,7 @@ static void on_connection(void* obj) client->ref = 1; client->server = server; client->quality = 10; /* default to lossless */ + client->led_state = -1; /* trigger sending of initial state */ int fd = accept(server->fd, NULL, 0); if (fd < 0) { @@ -2425,6 +2436,60 @@ bool nvnc_client_supports_cursor(const struct nvnc_client* client) return false; } +static bool client_send_led_state(struct nvnc_client* client) +{ + if (client->pending_led_state == client->led_state) + return false; + + bool have_qemu_led_state = + client_has_encoding(client, RFB_ENCODING_QEMU_LED_STATE); + bool have_vmware_led_state = + client_has_encoding(client, RFB_ENCODING_VMWARE_LED_STATE); + + if (!have_qemu_led_state && !have_vmware_led_state) + return false; + + nvnc_log(NVNC_LOG_DEBUG, "Keyboard LED state changed: %x -> %x", + client->led_state, client->pending_led_state); + + struct vec payload; + vec_init(&payload, 4096); + + struct rfb_server_fb_update_msg head = { + .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, + .n_rects = htons(1), + }; + + struct rfb_server_fb_rect rect = { + .encoding = htonl(RFB_ENCODING_QEMU_LED_STATE), + }; + + vec_append(&payload, &head, sizeof(head)); + vec_append(&payload, &rect, sizeof(rect)); + + if (have_qemu_led_state) { + uint8_t data = client->pending_led_state; + vec_append(&payload, &data, sizeof(data)); + } else if (have_vmware_led_state) { + uint32_t data = htonl(client->pending_led_state); + vec_append(&payload, &data, sizeof(data)); + } + + stream_send(client->net_stream, rcbuf_new(payload.data, payload.len), + NULL, NULL); + client->led_state = client->pending_led_state; + + return true; +} + +EXPORT +void nvnc_client_set_led_state(struct nvnc_client* client, + enum nvnc_keyboard_led_state state) +{ + client->pending_led_state = state; + process_fb_update_requests(client); +} + EXPORT void nvnc_set_name(struct nvnc* self, const char* name) {