diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2014-05-26 17:25:43 +0200 |
---|---|---|
committer | Victor Toso <me@victortoso.com> | 2016-09-30 14:27:11 +0200 |
commit | 22e32f29a66aec95651a8727ac730fdcb7cb1919 (patch) | |
tree | 9464063d702b72c5096d388d2df50919d08a3846 /src/vdagent | |
parent | 658fbb8ea6d81bd6188f1ad421341518c3662f8c (diff) |
build-sys: move user/system to respective dir
Signed-off-by: Victor Toso <victortoso@redhat.com>
Acked-by: Jonathon Jongsma <jjongsma@redhat.com>
Acked-by: Frediano Ziglio <fziglio@redhat.com>
Diffstat (limited to 'src/vdagent')
-rw-r--r-- | src/vdagent/vdagent-audio.c | 168 | ||||
-rw-r--r-- | src/vdagent/vdagent-audio.h | 27 | ||||
-rw-r--r-- | src/vdagent/vdagent-file-xfers.c | 325 | ||||
-rw-r--r-- | src/vdagent/vdagent-file-xfers.h | 43 | ||||
-rw-r--r-- | src/vdagent/vdagent-x11-priv.h | 155 | ||||
-rw-r--r-- | src/vdagent/vdagent-x11-randr.c | 981 | ||||
-rw-r--r-- | src/vdagent/vdagent-x11.c | 1356 | ||||
-rw-r--r-- | src/vdagent/vdagent-x11.h | 53 | ||||
-rw-r--r-- | src/vdagent/vdagent.c | 387 |
9 files changed, 3495 insertions, 0 deletions
diff --git a/src/vdagent/vdagent-audio.c b/src/vdagent/vdagent-audio.c new file mode 100644 index 0000000..6b11cd8 --- /dev/null +++ b/src/vdagent/vdagent-audio.c @@ -0,0 +1,168 @@ +/* vdagent-audio.c vdagentd audio handling code + + Copyright 2015 Red Hat, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <syslog.h> +#include <stdbool.h> +#include <alsa/asoundlib.h> +#include <alsa/mixer.h> +#include <alsa/error.h> +#include "vdagent-audio.h" + +#define ALSA_MUTE 0 +#define ALSA_UNMUTE 1 + +static snd_mixer_elem_t * +get_alsa_default_mixer_by_name(snd_mixer_t **handle, const char *name) +{ + snd_mixer_selem_id_t *sid; + int err = 0; + + if ((err = snd_mixer_open(handle, 0)) < 0) + goto fail; + + if ((err = snd_mixer_attach(*handle, "default")) < 0) + goto fail; + + if ((err = snd_mixer_selem_register(*handle, NULL, NULL)) < 0) + goto fail; + + if ((err = snd_mixer_load(*handle)) < 0) + goto fail; + + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, name); + return snd_mixer_find_selem(*handle, sid); + +fail: + syslog(LOG_WARNING, "%s fail: %s", __func__, snd_strerror(err)); + return NULL; +} + +static bool set_alsa_capture(uint8_t mute, uint8_t nchannels, uint16_t *volume) +{ + snd_mixer_t *handle = NULL; + snd_mixer_elem_t *e; + long min, max, vol; + bool ret = true; + int alsa_mute; + + e = get_alsa_default_mixer_by_name (&handle, "Capture"); + if (e == NULL) { + syslog(LOG_WARNING, "vdagent-audio: can't get default alsa mixer"); + ret = false; + goto end; + } + + alsa_mute = (mute) ? ALSA_MUTE : ALSA_UNMUTE; + snd_mixer_selem_set_capture_switch_all(e, alsa_mute); + + snd_mixer_selem_get_capture_volume_range(e, &min, &max); + switch (nchannels) { + case 1: /* MONO */ + vol = CLAMP(volume[0], min, max); + snd_mixer_selem_set_capture_volume(e, SND_MIXER_SCHN_MONO, vol); + syslog(LOG_DEBUG, "vdagent-audio: (capture-mono) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + break; + case 2: /* LEFT-RIGHT */ + vol = CLAMP(volume[0], min, max); + snd_mixer_selem_set_capture_volume(e, SND_MIXER_SCHN_FRONT_LEFT, vol); + syslog(LOG_DEBUG, "vdagent-audio: (capture-left) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + vol = CLAMP(volume[1], min, max); + snd_mixer_selem_set_capture_volume(e, SND_MIXER_SCHN_FRONT_RIGHT, vol); + syslog(LOG_DEBUG, "vdagent-audio: (capture-right) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + break; + default: + syslog(LOG_WARNING, "vdagent-audio: number of channels not supported"); + ret = false; + } +end: + if (handle != NULL) + snd_mixer_close(handle); + return ret; +} + +static bool set_alsa_playback (uint8_t mute, uint8_t nchannels, uint16_t *volume) +{ + snd_mixer_t *handle = NULL; + snd_mixer_elem_t* e; + long min, max, vol; + bool ret = true; + int alsa_mute; + + e = get_alsa_default_mixer_by_name (&handle, "Master"); + if (e == NULL) { + syslog(LOG_WARNING, "vdagent-audio: can't get default alsa mixer"); + ret = false; + goto end; + } + + alsa_mute = (mute) ? ALSA_MUTE : ALSA_UNMUTE; + snd_mixer_selem_set_playback_switch_all(e, alsa_mute); + + snd_mixer_selem_get_playback_volume_range(e, &min, &max); + switch (nchannels) { + case 1: /* MONO */ + vol = CLAMP(volume[0], min, max); + snd_mixer_selem_set_playback_volume(e, SND_MIXER_SCHN_MONO, vol); + syslog(LOG_DEBUG, "vdagent-audio: (playback-mono) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + break; + case 2: /* LEFT-RIGHT */ + vol = CLAMP(volume[0], min, max); + snd_mixer_selem_set_playback_volume(e, SND_MIXER_SCHN_FRONT_LEFT, vol); + syslog(LOG_DEBUG, "vdagent-audio: (playback-left) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + vol = CLAMP(volume[1], min, max); + snd_mixer_selem_set_playback_volume(e, SND_MIXER_SCHN_FRONT_RIGHT, vol); + syslog(LOG_DEBUG, "vdagent-audio: (playback-right) %lu (%%%0.2f)", + vol, (float) (100*vol/max)); + break; + default: + syslog(LOG_WARNING, "vdagent-audio: number of channels not supported"); + ret = false; + } +end: + if (handle != NULL) + snd_mixer_close(handle); + return ret; +} + +void vdagent_audio_playback_sync(uint8_t mute, uint8_t nchannels, uint16_t *volume) +{ + syslog(LOG_DEBUG, "%s mute=%s nchannels=%u", + __func__, (mute) ? "yes" : "no", nchannels); + if (set_alsa_playback (mute, nchannels, volume) == false) + syslog(LOG_WARNING, "Fail to sync playback volume"); +} + +void vdagent_audio_record_sync(uint8_t mute, uint8_t nchannels, uint16_t *volume) +{ + syslog(LOG_DEBUG, "%s mute=%s nchannels=%u", + __func__, (mute) ? "yes" : "no", nchannels); + if (set_alsa_capture (mute, nchannels, volume) == false) + syslog(LOG_WARNING, "Fail to sync record volume"); +} diff --git a/src/vdagent/vdagent-audio.h b/src/vdagent/vdagent-audio.h new file mode 100644 index 0000000..6f29d4b --- /dev/null +++ b/src/vdagent/vdagent-audio.h @@ -0,0 +1,27 @@ +/* vdagent-audio.h vdagentd audio handling header + + Copyright 2015 Red Hat, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __VDAGENT_AUDIO_H +#define __VDAGENT_AUDIO_H + +#include <stdio.h> +#include <stdint.h> + +void vdagent_audio_playback_sync(uint8_t mute, uint8_t nchannels, uint16_t *volume); +void vdagent_audio_record_sync(uint8_t mute, uint8_t nchannels, uint16_t *volume); + +#endif diff --git a/src/vdagent/vdagent-file-xfers.c b/src/vdagent/vdagent-file-xfers.c new file mode 100644 index 0000000..48b3069 --- /dev/null +++ b/src/vdagent/vdagent-file-xfers.c @@ -0,0 +1,325 @@ +/* vdagent file xfers code + + Copyright 2013 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede <hdegoede@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <spice/vd_agent.h> +#include <glib.h> + +#include "vdagentd-proto.h" +#include "vdagent-file-xfers.h" + +struct vdagent_file_xfers { + GHashTable *xfers; + struct udscs_connection *vdagentd; + char *save_dir; + int open_save_dir; + int debug; +}; + +typedef struct AgentFileXferTask { + uint32_t id; + int file_fd; + uint64_t read_bytes; + char *file_name; + uint64_t file_size; + int file_xfer_nr; + int file_xfer_total; + int debug; +} AgentFileXferTask; + +static void vdagent_file_xfer_task_free(gpointer data) +{ + AgentFileXferTask *task = data; + + g_return_if_fail(task != NULL); + + if (task->file_fd > 0) { + syslog(LOG_ERR, "file-xfer: Removing task %u and file %s due to error", + task->id, task->file_name); + close(task->file_fd); + unlink(task->file_name); + } else if (task->debug) + syslog(LOG_DEBUG, "file-xfer: Removing task %u %s", + task->id, task->file_name); + + g_free(task->file_name); + g_free(task); +} + +struct vdagent_file_xfers *vdagent_file_xfers_create( + struct udscs_connection *vdagentd, const char *save_dir, + int open_save_dir, int debug) +{ + struct vdagent_file_xfers *xfers; + + xfers = g_malloc(sizeof(*xfers)); + xfers->xfers = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, vdagent_file_xfer_task_free); + xfers->vdagentd = vdagentd; + xfers->save_dir = g_strdup(save_dir); + xfers->open_save_dir = open_save_dir; + xfers->debug = debug; + + return xfers; +} + +void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfers) +{ + g_return_if_fail(xfers != NULL); + + g_hash_table_destroy(xfers->xfers); + g_free(xfers->save_dir); + g_free(xfers); +} + +static AgentFileXferTask *vdagent_file_xfers_get_task( + struct vdagent_file_xfers *xfers, uint32_t id) +{ + AgentFileXferTask *task; + + g_return_val_if_fail(xfers != NULL, NULL); + + task = g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(id)); + if (task == NULL) + syslog(LOG_ERR, "file-xfer: error can not find task %u", id); + + return task; +} + +/* Parse start message then create a new file xfer task */ +static AgentFileXferTask *vdagent_parse_start_msg( + VDAgentFileXferStartMessage *msg) +{ + GKeyFile *keyfile = NULL; + AgentFileXferTask *task = NULL; + GError *error = NULL; + + keyfile = g_key_file_new(); + if (g_key_file_load_from_data(keyfile, + (const gchar *)msg->data, + -1, + G_KEY_FILE_NONE, &error) == FALSE) { + syslog(LOG_ERR, "file-xfer: failed to load keyfile: %s", + error->message); + goto error; + } + task = g_new0(AgentFileXferTask, 1); + task->id = msg->id; + task->file_name = g_key_file_get_string( + keyfile, "vdagent-file-xfer", "name", &error); + if (error) { + syslog(LOG_ERR, "file-xfer: failed to parse filename: %s", + error->message); + goto error; + } + task->file_size = g_key_file_get_uint64( + keyfile, "vdagent-file-xfer", "size", &error); + if (error) { + syslog(LOG_ERR, "file-xfer: failed to parse filesize: %s", + error->message); + goto error; + } + /* These are set for xfers which are part of a multi-file xfer */ + task->file_xfer_nr = g_key_file_get_integer( + keyfile, "vdagent-file-xfer", "file-xfer-nr", NULL); + task->file_xfer_total = g_key_file_get_integer( + keyfile, "vdagent-file-xfer", "file-xfer-total", NULL); + + g_key_file_free(keyfile); + return task; + +error: + g_clear_error(&error); + if (task) + vdagent_file_xfer_task_free(task); + if (keyfile) + g_key_file_free(keyfile); + return NULL; +} + +void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers, + VDAgentFileXferStartMessage *msg) +{ + AgentFileXferTask *task; + char *dir = NULL, *path = NULL, *file_path = NULL; + struct stat st; + int i; + + g_return_if_fail(xfers != NULL); + + if (g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(msg->id))) { + syslog(LOG_ERR, "file-xfer: error id %u already exists, ignoring!", + msg->id); + return; + } + + task = vdagent_parse_start_msg(msg); + if (task == NULL) { + goto error; + } + + task->debug = xfers->debug; + + file_path = g_build_filename(xfers->save_dir, task->file_name, NULL); + + dir = g_path_get_dirname(file_path); + if (g_mkdir_with_parents(dir, S_IRWXU) == -1) { + syslog(LOG_ERR, "file-xfer: Failed to create dir %s", dir); + goto error; + } + + path = g_strdup(file_path); + for (i = 0; i < 64 && (stat(path, &st) == 0 || errno != ENOENT); i++) { + g_free(path); + path = g_strdup_printf("%s (%d)", file_path, i + 1); + } + g_free(task->file_name); + task->file_name = path; + if (i == 64) { + syslog(LOG_ERR, "file-xfer: more then 63 copies of %s exist?", + file_path); + goto error; + } + + task->file_fd = open(path, O_CREAT | O_WRONLY, 0644); + if (task->file_fd == -1) { + syslog(LOG_ERR, "file-xfer: failed to create file %s: %s", + path, strerror(errno)); + goto error; + } + + if (ftruncate(task->file_fd, task->file_size) < 0) { + syslog(LOG_ERR, "file-xfer: err reserving %"PRIu64" bytes for %s: %s", + task->file_size, path, strerror(errno)); + goto error; + } + + g_hash_table_insert(xfers->xfers, GUINT_TO_POINTER(msg->id), task); + + if (xfers->debug) + syslog(LOG_DEBUG, "file-xfer: Adding task %u %s %"PRIu64" bytes", + task->id, path, task->file_size); + + udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS, + msg->id, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA, NULL, 0); + g_free(file_path); + g_free(dir); + return ; + +error: + udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS, + msg->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0); + if (task) + vdagent_file_xfer_task_free(task); + g_free(file_path); + g_free(dir); +} + +void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers, + VDAgentFileXferStatusMessage *msg) +{ + AgentFileXferTask *task; + + g_return_if_fail(xfers != NULL); + + task = vdagent_file_xfers_get_task(xfers, msg->id); + if (!task) + return; + + switch (msg->result) { + case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA: + syslog(LOG_ERR, "file-xfer: task %u %s received unexpected 0 response", + task->id, task->file_name); + break; + default: + /* Cancel or Error, remove this task */ + g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id)); + } +} + +void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers, + VDAgentFileXferDataMessage *msg) +{ + AgentFileXferTask *task; + int len, status = -1; + + g_return_if_fail(xfers != NULL); + + task = vdagent_file_xfers_get_task(xfers, msg->id); + if (!task) + return; + + len = write(task->file_fd, msg->data, msg->size); + if (len == msg->size) { + task->read_bytes += msg->size; + if (task->read_bytes >= task->file_size) { + if (task->read_bytes == task->file_size) { + if (xfers->debug) + syslog(LOG_DEBUG, "file-xfer: task %u %s has completed", + task->id, task->file_name); + close(task->file_fd); + task->file_fd = -1; + if (xfers->open_save_dir && + task->file_xfer_nr == task->file_xfer_total && + g_hash_table_size(xfers->xfers) == 1) { + char buf[PATH_MAX]; + snprintf(buf, PATH_MAX, "xdg-open '%s'&", xfers->save_dir); + status = system(buf); + } + status = VD_AGENT_FILE_XFER_STATUS_SUCCESS; + } else { + syslog(LOG_ERR, "file-xfer: error received too much data"); + status = VD_AGENT_FILE_XFER_STATUS_ERROR; + } + } + } else { + syslog(LOG_ERR, "file-xfer: error writing %s: %s", task->file_name, + strerror(errno)); + status = VD_AGENT_FILE_XFER_STATUS_ERROR; + } + + if (status != -1) { + udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS, + msg->id, status, NULL, 0); + g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id)); + } +} + +void vdagent_file_xfers_error(struct udscs_connection *vdagentd, uint32_t msg_id) +{ + g_return_if_fail(vdagentd != NULL); + + udscs_write(vdagentd, VDAGENTD_FILE_XFER_STATUS, + msg_id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0); +} diff --git a/src/vdagent/vdagent-file-xfers.h b/src/vdagent/vdagent-file-xfers.h new file mode 100644 index 0000000..28a71fd --- /dev/null +++ b/src/vdagent/vdagent-file-xfers.h @@ -0,0 +1,43 @@ +/* vdagent file xfers header + + Copyright 2013 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede <hdegoede@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __VDAGENT_FILE_XFERS_H +#define __VDAGENT_FILE_XFERS_H + +#include "udscs.h" + +struct vdagent_file_xfers; + +struct vdagent_file_xfers *vdagent_file_xfers_create( + struct udscs_connection *vdagentd, const char *save_dir, + int open_save_dir, int debug); +void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfer); + +void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers, + VDAgentFileXferStartMessage *msg); +void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers, + VDAgentFileXferStatusMessage *msg); +void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers, + VDAgentFileXferDataMessage *msg); +void vdagent_file_xfers_error(struct udscs_connection *vdagentd, + uint32_t msg_id); + +#endif diff --git a/src/vdagent/vdagent-x11-priv.h b/src/vdagent/vdagent-x11-priv.h new file mode 100644 index 0000000..d60cc07 --- /dev/null +++ b/src/vdagent/vdagent-x11-priv.h @@ -0,0 +1,155 @@ +#ifndef VDAGENT_X11_PRIV +#define VDAGENT_X11_PRIV + +#include <stdint.h> +#include <stdio.h> + +#include <spice/vd_agent.h> + +#include <X11/extensions/Xrandr.h> + +/* Macros to print a message to the logfile prefixed by the selection */ +#define SELPRINTF(format, ...) \ + syslog(LOG_ERR, "%s: " format, \ + vdagent_x11_sel_to_str(selection), ##__VA_ARGS__) + +#define VSELPRINTF(format, ...) \ + do { \ + if (x11->debug) { \ + syslog(LOG_DEBUG, "%s: " format, \ + vdagent_x11_sel_to_str(selection), ##__VA_ARGS__); \ + } \ + } while (0) + +#define MAX_SCREENS 16 +/* Same as qxl_dev.h client_monitors_config.heads count */ +#define MONITOR_SIZE_COUNT 64 + +enum { owner_none, owner_guest, owner_client }; + +/* X11 terminology is confusing a selection request is a request from an + app to get clipboard data from us, so iow from the spice client through + the vdagent channel. We handle these one at a time and queue any which + come in while we are still handling the current one. */ +struct vdagent_x11_selection_request { + XEvent event; + uint8_t selection; + struct vdagent_x11_selection_request *next; +}; + +/* A conversion request is X11 speak for asking an other app to give its + clipboard data to us, we do these on behalf of the spice client to copy + data from the guest to the client. Like selection requests we process + these one at a time. */ +struct vdagent_x11_conversion_request { + Atom target; + uint8_t selection; + struct vdagent_x11_conversion_request *next; +}; + +struct clipboard_format_tmpl { + uint32_t type; + const char *atom_names[16]; +}; + +struct clipboard_format_info { + uint32_t type; + Atom atoms[16]; + int atom_count; +}; + +struct monitor_size { + int width; + int height; +}; + +static const struct clipboard_format_tmpl clipboard_format_templates[] = { + { VD_AGENT_CLIPBOARD_UTF8_TEXT, { "UTF8_STRING", "text/plain;charset=UTF-8", + "text/plain;charset=utf-8", "STRING", NULL }, }, + { VD_AGENT_CLIPBOARD_IMAGE_PNG, { "image/png", NULL }, }, + { VD_AGENT_CLIPBOARD_IMAGE_BMP, { "image/bmp", "image/x-bmp", + "image/x-MS-bmp", "image/x-win-bitmap", NULL }, }, + { VD_AGENT_CLIPBOARD_IMAGE_TIFF, { "image/tiff", NULL }, }, + { VD_AGENT_CLIPBOARD_IMAGE_JPG, { "image/jpeg", NULL }, }, +}; + +#define clipboard_format_count (sizeof(clipboard_format_templates)/sizeof(clipboard_format_templates[0])) + +struct vdagent_x11 { + struct clipboard_format_info clipboard_formats[clipboard_format_count]; + Display *display; + Atom clipboard_atom; + Atom clipboard_primary_atom; + Atom targets_atom; + Atom incr_atom; + Atom multiple_atom; + Atom timestamp_atom; + Window root_window[MAX_SCREENS]; + Window selection_window; + struct udscs_connection *vdagentd; + char *net_wm_name; + int debug; + int fd; + int screen_count; + int width[MAX_SCREENS]; + int height[MAX_SCREENS]; + int has_xfixes; + int xfixes_event_base; + int xrandr_event_base; + int max_prop_size; + int expected_targets_notifies[256]; + int clipboard_owner[256]; + int clipboard_type_count[256]; + uint32_t clipboard_agent_types[256][256]; + Atom clipboard_x11_targets[256][256]; + /* Data for conversion_req which is currently being processed */ + struct vdagent_x11_conversion_request *conversion_req; + int expect_property_notify; + uint8_t *clipboard_data; + uint32_t clipboard_data_size; + uint32_t clipboard_data_space; + /* Data for selection_req which is currently being processed */ + struct vdagent_x11_selection_request *selection_req; + uint8_t *selection_req_data; + uint32_t selection_req_data_pos; + uint32_t selection_req_data_size; + Atom selection_req_atom; + /* resolution change state */ + struct { + XRRScreenResources *res; + XRROutputInfo **outputs; + XRRCrtcInfo **crtcs; + int min_width; + int max_width; + int min_height; + int max_height; + int num_monitors; + struct monitor_size monitor_sizes[MONITOR_SIZE_COUNT]; + VDAgentMonitorsConfig *failed_conf; + } randr; + + /* NB: we cache this assuming the driver isn't changed under our feet */ + int set_crtc_config_not_functional; + + int has_xrandr; + int xrandr_major; + int xrandr_minor; + int has_xinerama; + int dont_send_guest_xorg_res; +}; + +extern int (*vdagent_x11_prev_error_handler)(Display *, XErrorEvent *); +extern int vdagent_x11_caught_error; + +void vdagent_x11_randr_init(struct vdagent_x11 *x11); +void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11 *x11, + int update); +void vdagent_x11_randr_handle_root_size_change(struct vdagent_x11 *x11, + int screen, int width, int height); +int vdagent_x11_randr_handle_event(struct vdagent_x11 *x11, + XEvent event); +void vdagent_x11_set_error_handler(struct vdagent_x11 *x11, + int (*handler)(Display *, XErrorEvent *)); +int vdagent_x11_restore_error_handler(struct vdagent_x11 *x11); + +#endif // VDAGENT_X11_PRIV diff --git a/src/vdagent/vdagent-x11-randr.c b/src/vdagent/vdagent-x11-randr.c new file mode 100644 index 0000000..02089b2 --- /dev/null +++ b/src/vdagent/vdagent-x11-randr.c @@ -0,0 +1,981 @@ +/* vdagent-x11-randr.c vdagent Xrandr integration code + + Copyright 2012 Red Hat, Inc. + + Red Hat Authors: + Alon Levy <alevy@redhat.com> + Hans de Goede <hdegoede@redhat.com> + Marc-AndrĂ© Lureau <mlureau@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <glib.h> +#include <glib/gstdio.h> +#include <string.h> +#include <syslog.h> +#include <stdlib.h> +#include <limits.h> + +#include <X11/extensions/Xinerama.h> + +#include "vdagentd-proto.h" +#include "vdagent-x11.h" +#include "vdagent-x11-priv.h" + +#define MM_PER_INCH (25.4) + +static int error_handler(Display *display, XErrorEvent *error) +{ + vdagent_x11_caught_error = 1; + return 0; +} + +static XRRModeInfo *mode_from_id(struct vdagent_x11 *x11, int id) +{ + int i; + + for (i = 0 ; i < x11->randr.res->nmode ; ++i) { + if (id == x11->randr.res->modes[i].id) { + return &x11->randr.res->modes[i]; + } + } + return NULL; +} + +static XRRCrtcInfo *crtc_from_id(struct vdagent_x11 *x11, int id) +{ + int i; + + for (i = 0 ; i < x11->randr.res->ncrtc ; ++i) { + if (id == x11->randr.res->crtcs[i]) { + return x11->randr.crtcs[i]; + } + } + return NULL; +} + +static void free_randr_resources(struct vdagent_x11 *x11) +{ + int i; + + if (!x11->randr.res) { + return; + } + if (x11->randr.outputs != NULL) { + for (i = 0 ; i < x11->randr.res->noutput; ++i) { + XRRFreeOutputInfo(x11->randr.outputs[i]); + } + free(x11->randr.outputs); + } + if (x11->randr.crtcs != NULL) { + for (i = 0 ; i < x11->randr.res->ncrtc; ++i) { + XRRFreeCrtcInfo(x11->randr.crtcs[i]); + } + free(x11->randr.crtcs); + } + XRRFreeScreenResources(x11->randr.res); + x11->randr.res = NULL; + x11->randr.outputs = NULL; + x11->randr.crtcs = NULL; + x11->randr.num_monitors = 0; +} + +static void update_randr_res(struct vdagent_x11 *x11, int poll) +{ + int i; + + free_randr_resources(x11); + if (poll) + x11->randr.res = XRRGetScreenResources(x11->display, x11->root_window[0]); + else + x11->randr.res = XRRGetScreenResourcesCurrent(x11->display, x11->root_window[0]); + x11->randr.outputs = malloc(x11->randr.res->noutput * sizeof(*x11->randr.outputs)); + x11->randr.crtcs = malloc(x11->randr.res->ncrtc * sizeof(*x11->randr.crtcs)); + for (i = 0 ; i < x11->randr.res->noutput; ++i) { + x11->randr.outputs[i] = XRRGetOutputInfo(x11->display, x11->randr.res, + x11->randr.res->outputs[i]); + if (x11->randr.outputs[i]->connection == RR_Connected) + x11->randr.num_monitors++; + } + for (i = 0 ; i < x11->randr.res->ncrtc; ++i) { + x11->randr.crtcs[i] = XRRGetCrtcInfo(x11->display, x11->randr.res, + x11->randr.res->crtcs[i]); + } + /* XXX is this dynamic? should it be cached? */ + if (XRRGetScreenSizeRange(x11->display, x11->root_window[0], + &x11->randr.min_width, + &x11->randr.min_height, + &x11->randr.max_width, + &x11->randr.max_height) != 1) { + syslog(LOG_ERR, "update_randr_res: RRGetScreenSizeRange failed"); + } +} + +void vdagent_x11_randr_init(struct vdagent_x11 *x11) +{ + int i; + + if (x11->screen_count > 1) { + syslog(LOG_WARNING, "X-server has more then 1 screen, " + "disabling client -> guest resolution syncing"); + return; + } + + if (XRRQueryExtension(x11->display, &x11->xrandr_event_base, &i)) { + XRRQueryVersion(x11->display, &x11->xrandr_major, &x11->xrandr_minor); + if (x11->xrandr_major == 1 && x11->xrandr_minor >= 3) + x11->has_xrandr = 1; + } + + XRRSelectInput(x11->display, x11->root_window[0], + RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask); + + if (x11->has_xrandr) { + update_randr_res(x11, 0); + } else { + x11->randr.res = NULL; + } + + if (XineramaQueryExtension(x11->display, &i, &i)) + x11->has_xinerama = 1; + + switch (x11->has_xrandr << 4 | x11->has_xinerama) { + case 0x00: + syslog(LOG_ERR, "Neither Xrandr nor Xinerama found, assuming single monitor setup"); + break; + case 0x01: + if (x11->debug) + syslog(LOG_DEBUG, "Found Xinerama extension without Xrandr, assuming Xinerama multi monitor setup"); + break; + case 0x10: + syslog(LOG_ERR, "Found Xrandr but no Xinerama, weird!"); + break; + case 0x11: + /* Standard xrandr setup, nothing to see here */ + break; + } +} + +static XRRModeInfo * +find_mode_by_name (struct vdagent_x11 *x11, char *name) +{ + int m; + XRRModeInfo *ret = NULL; + + for (m = 0; m < x11->randr.res->nmode; m++) { + XRRModeInfo *mode = &x11->randr.res->modes[m]; + if (!strcmp (name, mode->name)) { + ret = mode; + break; + } + } + return ret; +} + +static XRRModeInfo * +find_mode_by_size (struct vdagent_x11 *x11, int output, int width, int height) +{ + int m; + XRRModeInfo *ret = NULL; + + for (m = 0; m < x11->randr.outputs[output]->nmode; m++) { + XRRModeInfo *mode = mode_from_id(x11, + x11->randr.outputs[output]->modes[m]); + if (mode && mode->width == width && mode->height == height) { + ret = mode; + break; + } + } + return ret; +} + +static void delete_mode(struct vdagent_x11 *x11, int output_index, + int width, int height) +{ + int m; + XRRModeInfo *mode; + XRROutputInfo *output_info; + char name[20]; + + if (width == 0 || height == 0) + return; + + snprintf(name, sizeof(name), "%dx%d-%d", width, height, output_index); + if (x11->debug) + syslog(LOG_DEBUG, "Deleting mode %s", name); + + output_info = x11->randr.outputs[output_index]; + if (output_info->ncrtc != 1) { + syslog(LOG_ERR, "output has %d crtcs, expected exactly 1, " + "failed to delete mode", output_info->ncrtc); + return; + } + for (m = 0 ; m < x11->randr.res->nmode; ++m) { + mode = &x11->randr.res->modes[m]; + if (strcmp(mode->name, name) == 0) + break; + } + if (m < x11->randr.res->nmode) { + vdagent_x11_set_error_handler(x11, error_handler); + XRRDeleteOutputMode (x11->display, x11->randr.res->outputs[output_index], + mode->id); + XRRDestroyMode (x11->display, mode->id); + // ignore race error, if mode is created by others + vdagent_x11_restore_error_handler(x11); + } + + /* silly to update everytime for more then one monitor */ + update_randr_res(x11, 0); +} + +static void set_reduced_cvt_mode(XRRModeInfo *mode, int width, int height) +{ + /* Code taken from hw/xfree86/modes/xf86cvt.c + * See that file for lineage. Originated in public domain code + * Would be nice if xorg exported this in a library */ + + /* 1) top/bottom margin size (% of height) - default: 1.8 */ +#define CVT_MARGIN_PERCENTAGE 1.8 + + /* 2) character cell horizontal granularity (pixels) - default 8 */ +#define CVT_H_GRANULARITY 8 + + /* 4) Minimum vertical porch (lines) - default 3 */ +#define CVT_MIN_V_PORCH 3 + + /* 4) Minimum number of vertical back porch lines - default 6 */ +#define CVT_MIN_V_BPORCH 6 + + /* Pixel Clock step (kHz) */ +#define CVT_CLOCK_STEP 250 + + /* Minimum vertical blanking interval time (µs) - default 460 */ +#define CVT_RB_MIN_VBLANK 460.0 + + /* Fixed number of clocks for horizontal sync */ +#define CVT_RB_H_SYNC 32.0 + + /* Fixed number of clocks for horizontal blanking */ +#define CVT_RB_H_BLANK 160.0 + + /* Fixed number of lines for vertical front porch - default 3 */ +#define CVT_RB_VFPORCH 3 + + int VBILines; + float VFieldRate = 60.0; + int VSync; + float HPeriod; + + /* 2. Horizontal pixels */ + width = width - (width % CVT_H_GRANULARITY); + + mode->width = width; + mode->height = height; + VSync = 10; + + /* 8. Estimate Horizontal period. */ + HPeriod = ((float) (1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / height; + + /* 9. Find number of lines in vertical blanking */ + VBILines = ((float) CVT_RB_MIN_VBLANK) / HPeriod + 1; + + /* 10. Check if vertical blanking is sufficient */ + if (VBILines < (CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH)) + VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH; + + /* 11. Find total number of lines in vertical field */ + mode->vTotal = height + VBILines; + + /* 12. Find total number of pixels in a line */ + mode->hTotal = mode->width + CVT_RB_H_BLANK; + + /* Fill in HSync values */ + mode->hSyncEnd = mode->width + CVT_RB_H_BLANK / 2; + mode->hSyncStart = mode->hSyncEnd - CVT_RB_H_SYNC; + + /* Fill in VSync values */ + mode->vSyncStart = mode->height + CVT_RB_VFPORCH; + mode->vSyncEnd = mode->vSyncStart + VSync; + + /* 15/13. Find pixel clock frequency (kHz for xf86) */ + mode->dotClock = mode->hTotal * 1000.0 / HPeriod; + mode->dotClock -= mode->dotClock % CVT_CLOCK_STEP; + +} + +static XRRModeInfo *create_new_mode(struct vdagent_x11 *x11, int output_index, + int width, int height) +{ + char modename[20]; + XRRModeInfo mode; + + snprintf(modename, sizeof(modename), "%dx%d-%d", width, height, output_index); + mode.hSkew = 0; + mode.name = modename; + mode.nameLength = strlen(mode.name); + set_reduced_cvt_mode(&mode, width, height); + mode.modeFlags = 0; + mode.id = 0; + vdagent_x11_set_error_handler(x11, error_handler); + XRRCreateMode (x11->display, x11->root_window[0], &mode); + // ignore race error, if mode is created by others + vdagent_x11_restore_error_handler(x11); + + /* silly to update everytime for more then one monitor */ + update_randr_res(x11, 0); + + return find_mode_by_name(x11, modename); +} + +static int xrandr_add_and_set(struct vdagent_x11 *x11, int output, int x, int y, + int width, int height) +{ + XRRModeInfo *mode; + int xid; + Status s; + RROutput outputs[1]; + int old_width = x11->randr.monitor_sizes[output].width; + int old_height = x11->randr.monitor_sizes[output].height; + + if (!x11->randr.res || output >= x11->randr.res->noutput || output < 0) { + syslog(LOG_ERR, "%s: program error: missing RANDR or bad output", + __FUNCTION__); + return 0; + } + if (x11->set_crtc_config_not_functional) { + /* fail, set_best_mode will find something close. */ + return 0; + } + xid = x11->randr.res->outputs[output]; + mode = find_mode_by_size(x11, output, width, height); + if (!mode) { + mode = create_new_mode(x11, output, width, height); + } + if (!mode) { + syslog(LOG_ERR, "failed to add a new mode"); + return 0; + } + XRRAddOutputMode(x11->display, xid, mode->id); + x11->randr.monitor_sizes[output].width = width; + x11->randr.monitor_sizes[output].height = height; + outputs[0] = xid; + vdagent_x11_set_error_handler(x11, error_handler); + s = XRRSetCrtcConfig(x11->display, x11->randr.res, x11->randr.res->crtcs[output], + CurrentTime, x, y, mode->id, RR_Rotate_0, outputs, + 1); + if (vdagent_x11_restore_error_handler(x11) || (s != RRSetConfigSuccess)) { + syslog(LOG_ERR, "failed to XRRSetCrtcConfig"); + x11->set_crtc_config_not_functional = 1; + return 0; + } + + /* clean the previous name, if any */ + if (width != old_width || height != old_height) + delete_mode(x11, output, old_width, old_height); + + return 1; +} + +static void xrandr_disable_output(struct vdagent_x11 *x11, int output) +{ + Status s; + + if (!x11->randr.res || output >= x11->randr.res->noutput || output < 0) { + syslog(LOG_ERR, "%s: program error: missing RANDR or bad output", + __FUNCTION__); + return; + } + + s = XRRSetCrtcConfig(x11->display, x11->randr.res, + x11->randr.res->crtcs[output], + CurrentTime, 0, 0, None, RR_Rotate_0, + NULL, 0); + + if (s != RRSetConfigSuccess) + syslog(LOG_ERR, "failed to disable monitor"); + + delete_mode(x11, output, x11->randr.monitor_sizes[output].width, + x11->randr.monitor_sizes[output].height); + x11->randr.monitor_sizes[output].width = 0; + x11->randr.monitor_sizes[output].height = 0; +} + +static int set_screen_to_best_size(struct vdagent_x11 *x11, int width, int height, + int *out_width, int *out_height){ + int i, num_sizes = 0; + int best = -1; + unsigned int closest_diff = -1; + XRRScreenSize *sizes; + XRRScreenConfiguration *config; + Rotation rotation; + + sizes = XRRSizes(x11->display, 0, &num_sizes); + if (!sizes || !num_sizes) { + syslog(LOG_ERR, "XRRSizes failed"); + return 0; + } + if (x11->debug) + syslog(LOG_DEBUG, "set_screen_to_best_size found %d modes\n", num_sizes); + + /* Find the closest size which will fit within the monitor */ + for (i = 0; i < num_sizes; i++) { + if (sizes[i].width > width || + sizes[i].height > height) + continue; /* Too large for the monitor */ + + unsigned int wdiff = width - sizes[i].width; + unsigned int hdiff = height - sizes[i].height; + unsigned int diff = wdiff * wdiff + hdiff * hdiff; + if (diff < closest_diff) { + closest_diff = diff; + best = i; + } + } + + if (best == -1) { + syslog(LOG_ERR, "no suitable resolution found for monitor"); + return 0; + } + + config = XRRGetScreenInfo(x11->display, x11->root_window[0]); + if(!config) { + syslog(LOG_ERR, "get screen info failed"); + return 0; + } + XRRConfigCurrentConfiguration(config, &rotation); + XRRSetScreenConfig(x11->display, config, x11->root_window[0], best, + rotation, CurrentTime); + XRRFreeScreenConfigInfo(config); + + if (x11->debug) + syslog(LOG_DEBUG, "set_screen_to_best_size set size to: %dx%d\n", + sizes[best].width, sizes[best].height); + *out_width = sizes[best].width; + *out_height = sizes[best].height; + return 1; +} + +void vdagent_x11_randr_handle_root_size_change(struct vdagent_x11 *x11, + int screen, int width, int height) +{ + update_randr_res(x11, 0); + + if (width == x11->width[screen] && height == x11->height[screen]) { + return; + } + + if (x11->debug) + syslog(LOG_DEBUG, "Root size of screen %d changed to %dx%d send %d", + screen, width, height, !x11->dont_send_guest_xorg_res); + + x11->width[screen] = width; + x11->height[screen] = height; + if (!x11->dont_send_guest_xorg_res) { + vdagent_x11_send_daemon_guest_xorg_res(x11, 1); + } +} + +int vdagent_x11_randr_handle_event(struct vdagent_x11 *x11, + XEvent event) +{ + int handled = TRUE; + + switch (event.type - x11->xrandr_event_base) { + case RRScreenChangeNotify: { + XRRScreenChangeNotifyEvent *sce = + (XRRScreenChangeNotifyEvent *) &event; + vdagent_x11_randr_handle_root_size_change(x11, 0, + sce->width, sce->height); + break; + } + case RRNotify: { + update_randr_res(x11, 0); + if (!x11->dont_send_guest_xorg_res) + vdagent_x11_send_daemon_guest_xorg_res(x11, 1); + break; + } + default: + handled = FALSE; + break; + } + + return handled; +} + +static int min_int(int x, int y) +{ + return x > y ? y : x; +} + +static int max_int(int x, int y) +{ + return x > y ? x : y; +} + +static int constrain_to_range(int low, int *val, int high) +{ + if (low <= *val && *val <= high) { + return 0; + } + if (low > *val) { + *val = low; + } + if (*val > high) { + *val = high; + } + return 1; +} + +static void constrain_to_screen(struct vdagent_x11 *x11, int *w, int *h) +{ + int lx, ly, hx, hy; + int orig_h = *h; + int orig_w = *w; + + lx = x11->randr.min_width; + hx = x11->randr.max_width; + ly = x11->randr.min_height; + hy = x11->randr.max_height; + if (constrain_to_range(lx, w, hx)) { + syslog(LOG_ERR, "width not in driver range: ! %d < %d < %d", + lx, orig_w, hx); + } + if (constrain_to_range(ly, h, hy)) { + syslog(LOG_ERR, "height not in driver range: ! %d < %d < %d", + ly, orig_h, hy); + } +} + +static int monitor_enabled(VDAgentMonConfig *mon) +{ + return mon->width != 0 && mon->height != 0; +} + +/* + * The agent config doesn't contain a primary size, just the monitors, but + * we need to total size as well, to make sure we have enough memory and + * because X needs it. + * + * At the same pass constrain any provided size to what the server accepts. + * + * Exit axioms: + * x >= 0, y >= 0 for all x, y in mon_config + * max_width >= width >= min_width, + * max_height >= height >= min_height for all monitors in mon_config + */ +static void zero_base_monitors(struct vdagent_x11 *x11, + VDAgentMonitorsConfig *mon_config, + int *width, int *height) +{ + int i, min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN; + int *mon_height, *mon_width; + + for (i = 0; i < mon_config->num_of_monitors; i++) { + if (!monitor_enabled(&mon_config->monitors[i])) + continue; + mon_config->monitors[i].x &= ~7; + mon_config->monitors[i].width &= ~7; + mon_width = (int *)&mon_config->monitors[i].width; + mon_height = (int *)&mon_config->monitors[i].height; + constrain_to_screen(x11, mon_width, mon_height); + min_x = min_int(mon_config->monitors[i].x, min_x); + min_y = min_int(mon_config->monitors[i].y, min_y); + max_x = max_int(mon_config->monitors[i].x + *mon_width, max_x); + max_y = max_int(mon_config->monitors[i].y + *mon_height, max_y); + } + if (min_x != 0 || min_y != 0) { + syslog(LOG_ERR, "%s: agent config %d,%d rooted, adjusting to 0,0.", + __FUNCTION__, min_x, min_y); + for (i = 0 ; i < mon_config->num_of_monitors; ++i) { + if (!monitor_enabled(&mon_config->monitors[i])) + continue; + mon_config->monitors[i].x -= min_x; + mon_config->monitors[i].y -= min_y; + } + } + max_x -= min_x; + max_y -= min_y; + *width = max_x; + *height = max_y; +} + +static int enabled_monitors(VDAgentMonitorsConfig *mon) +{ + int i, enabled = 0; + + for (i = 0; i < mon->num_of_monitors; i++) { + if (monitor_enabled(&mon->monitors[i])) + enabled++; + } + return enabled; +} + +static int same_monitor_configs(VDAgentMonitorsConfig *conf1, + VDAgentMonitorsConfig *conf2) +{ + int i; + + if (conf1 == NULL || conf2 == NULL || + conf1->num_of_monitors != conf2->num_of_monitors) + return 0; + + for (i = 0; i < conf1->num_of_monitors; i++) { + VDAgentMonConfig *mon1 = &conf1->monitors[i]; + VDAgentMonConfig *mon2 = &conf2->monitors[i]; + /* NOTE: we don't compare depth. */ + if (mon1->x != mon2->x || mon1->y != mon2->y || + mon1->width != mon2->width || mon1->height != mon2->height) + return 0; + } + return 1; +} + +static int config_size(int num_of_monitors) +{ + return sizeof(VDAgentMonitorsConfig) + + num_of_monitors * sizeof(VDAgentMonConfig); +} + +static VDAgentMonitorsConfig *get_current_mon_config(struct vdagent_x11 *x11) +{ + int i, num_of_monitors = 0; + XRRModeInfo *mode; + XRRCrtcInfo *crtc; + XRRScreenResources *res = x11->randr.res; + VDAgentMonitorsConfig *mon_config; + + mon_config = calloc(1, config_size(res->noutput)); + if (!mon_config) { + syslog(LOG_ERR, "out of memory allocating current monitor config"); + return NULL; + } + + for (i = 0 ; i < res->noutput; i++) { + if (x11->randr.outputs[i]->ncrtc == 0) + continue; /* Monitor disabled, already zero-ed by calloc */ + if (x11->randr.outputs[i]->ncrtc != 1) + goto error; + + crtc = crtc_from_id(x11, x11->randr.outputs[i]->crtcs[0]); + if (!crtc) + goto error; + + mode = mode_from_id(x11, crtc->mode); + if (!mode) + continue; /* Monitor disabled, already zero-ed by calloc */ + + mon_config->monitors[i].x = crtc->x; + mon_config->monitors[i].y = crtc->y; + mon_config->monitors[i].width = mode->width; + mon_config->monitors[i].height = mode->height; + num_of_monitors = i + 1; + } + mon_config->num_of_monitors = num_of_monitors; + mon_config->flags = VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS; + return mon_config; + +error: + syslog(LOG_ERR, "error: inconsistent or stale data from X"); + free(mon_config); + return NULL; +} + +static void dump_monitors_config(struct vdagent_x11 *x11, + VDAgentMonitorsConfig *mon_config, + const char *prefix) +{ + int i; + VDAgentMonConfig *m; + + syslog(LOG_DEBUG, "%s: %d, %x", prefix, mon_config->num_of_monitors, + mon_config->flags); + for (i = 0 ; i < mon_config->num_of_monitors; ++i) { + m = &mon_config->monitors[i]; + if (!monitor_enabled(m)) + continue; + syslog(LOG_DEBUG, "received monitor %d config %dx%d+%d+%d", i, + m->width, m->height, m->x, m->y); + } +} + +/* + * Set monitor configuration according to client request. + * + * On exit send current configuration to client, regardless of error. + * + * Errors: + * screen size too large for driver to handle. (we set the largest/smallest possible) + * no randr support in X server. + * invalid configuration request from client. + */ +void vdagent_x11_set_monitor_config(struct vdagent_x11 *x11, + VDAgentMonitorsConfig *mon_config, + int fallback) +{ + int primary_w, primary_h; + int i, real_num_of_monitors = 0; + VDAgentMonitorsConfig *curr = NULL; + + if (!x11->has_xrandr) + goto exit; + + if (enabled_monitors(mon_config) < 1) { + syslog(LOG_ERR, "client sent config with all monitors disabled"); + goto exit; + } + + if (x11->debug) { + dump_monitors_config(x11, mon_config, "from guest"); + } + + for (i = 0; i < mon_config->num_of_monitors; i++) { + if (monitor_enabled(&mon_config->monitors[i])) + real_num_of_monitors = i + 1; + } + mon_config->num_of_monitors = real_num_of_monitors; + + update_randr_res(x11, 0); + if (mon_config->num_of_monitors > x11->randr.res->noutput) { + syslog(LOG_WARNING, + "warning unexpected client request: #mon %d > driver output %d", + mon_config->num_of_monitors, x11->randr.res->noutput); + mon_config->num_of_monitors = x11->randr.res->noutput; + } + + if (mon_config->num_of_monitors > MONITOR_SIZE_COUNT) { + syslog(LOG_WARNING, "warning: client send %d monitors, capping at %d", + mon_config->num_of_monitors, MONITOR_SIZE_COUNT); + mon_config->num_of_monitors = MONITOR_SIZE_COUNT; + } + + zero_base_monitors(x11, mon_config, &primary_w, &primary_h); + + constrain_to_screen(x11, &primary_w, &primary_h); + + if (x11->debug) { + dump_monitors_config(x11, mon_config, "after zeroing"); + } + + curr = get_current_mon_config(x11); + if (!curr) + goto exit; + if (same_monitor_configs(mon_config, curr) && + x11->width[0] == primary_w && x11->height[0] == primary_h) { + goto exit; + } + + if (same_monitor_configs(mon_config, x11->randr.failed_conf)) { + syslog(LOG_WARNING, "Ignoring previous failed client monitor config"); + goto exit; + } + + gchar *config = g_build_filename (g_get_user_config_dir (), "monitors.xml", NULL); + g_unlink(config); + g_free(config); + + for (i = mon_config->num_of_monitors; i < x11->randr.res->noutput; i++) + xrandr_disable_output(x11, i); + + /* First, disable disabled CRTCs... */ + for (i = 0; i < mon_config->num_of_monitors; ++i) { + if (!monitor_enabled(&mon_config->monitors[i])) { + xrandr_disable_output(x11, i); + } + } + + /* ... and disable the ones that would be bigger than + * the new RandR screen once it is resized. If they are enabled the + * XRRSetScreenSize call will fail with BadMatch. They will be + * reenabled after hanging the screen size. + */ + for (i = 0; i < curr->num_of_monitors; ++i) { + int width, height; + int x, y; + + width = curr->monitors[i].width; + height = curr->monitors[i].height; + x = curr->monitors[i].x; + y = curr->monitors[i].y; + + if ((x + width > primary_w) || (y + height > primary_h)) { + if (x11->debug) + syslog(LOG_DEBUG, "Disabling monitor %d: " + "%dx%d+%d+%d > (%d,%d)", + i, width, height, x, y, primary_w, primary_h); + + xrandr_disable_output(x11, i); + } + } + + /* Then we can resize the RandR screen. */ + if (primary_w != x11->width[0] || primary_h != x11->height[0]) { + const int dpi = 96; /* FIXME: read settings from desktop or get from client dpi? */ + int width_mm = (MM_PER_INCH * primary_w) / dpi; + int height_mm = (MM_PER_INCH * primary_h) / dpi; + + if (x11->debug) + syslog(LOG_DEBUG, "Changing screen size to %dx%d", + primary_w, primary_h); + vdagent_x11_set_error_handler(x11, error_handler); + XRRSetScreenSize(x11->display, x11->root_window[0], primary_w, primary_h, + width_mm, height_mm); + if (vdagent_x11_restore_error_handler(x11)) { + syslog(LOG_ERR, "XRRSetScreenSize failed, not enough mem?"); + if (!fallback) { + syslog(LOG_WARNING, "Restoring previous config"); + vdagent_x11_set_monitor_config(x11, curr, 1); + free(curr); + /* Remember this config failed, if the client is maximized or + fullscreen it will keep sending the failing config. */ + free(x11->randr.failed_conf); + x11->randr.failed_conf = + malloc(config_size(mon_config->num_of_monitors)); + if (x11->randr.failed_conf) + memcpy(x11->randr.failed_conf, mon_config, + config_size(mon_config->num_of_monitors)); + return; + } + } + } + + /* Finally, we set the new resolutions on RandR CRTCs now that the + * RandR screen is big enough to hold these. */ + for (i = 0; i < mon_config->num_of_monitors; ++i) { + int width, height; + int x, y; + + if (!monitor_enabled(&mon_config->monitors[i])) { + continue; + } + /* Try to create the requested resolution */ + width = mon_config->monitors[i].width; + height = mon_config->monitors[i].height; + x = mon_config->monitors[i].x; + y = mon_config->monitors[i].y; + if (!xrandr_add_and_set(x11, i, x, y, width, height) && + enabled_monitors(mon_config) == 1) { + set_screen_to_best_size(x11, width, height, + &primary_w, &primary_h); + break; + } + } + + update_randr_res(x11, + x11->randr.num_monitors != enabled_monitors(mon_config)); + x11->width[0] = primary_w; + x11->height[0] = primary_h; + + /* Flush output buffers and consume any pending events (ConfigureNotify) */ + x11->dont_send_guest_xorg_res = 1; + vdagent_x11_do_read(x11); + x11->dont_send_guest_xorg_res = 0; + +exit: + vdagent_x11_send_daemon_guest_xorg_res(x11, 0); + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); + free(curr); +} + +void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11 *x11, int update) +{ + struct vdagentd_guest_xorg_resolution *res = NULL; + int i, width = 0, height = 0, screen_count = 0; + + if (x11->has_xrandr) { + VDAgentMonitorsConfig *curr; + + if (update) + update_randr_res(x11, 0); + + curr = get_current_mon_config(x11); + if (!curr) + goto no_info; + + screen_count = curr->num_of_monitors; + res = malloc(screen_count * sizeof(*res)); + if (!res) { + free(curr); + goto no_mem; + } + + for (i = 0; i < screen_count; i++) { + res[i].width = curr->monitors[i].width; + res[i].height = curr->monitors[i].height; + res[i].x = curr->monitors[i].x; + res[i].y = curr->monitors[i].y; + } + free(curr); + width = x11->width[0]; + height = x11->height[0]; + } else if (x11->has_xinerama) { + XineramaScreenInfo *screen_info = NULL; + + screen_info = XineramaQueryScreens(x11->display, &screen_count); + if (!screen_info) + goto no_info; + res = malloc(screen_count * sizeof(*res)); + if (!res) { + XFree(screen_info); + goto no_mem; + } + for (i = 0; i < screen_count; i++) { + if (screen_info[i].screen_number >= screen_count) { + syslog(LOG_ERR, "Invalid screen number in xinerama screen info (%d >= %d)", + screen_info[i].screen_number, screen_count); + XFree(screen_info); + free(res); + return; + } + res[screen_info[i].screen_number].width = screen_info[i].width; + res[screen_info[i].screen_number].height = screen_info[i].height; + res[screen_info[i].screen_number].x = screen_info[i].x_org; + res[screen_info[i].screen_number].y = screen_info[i].y_org; + } + XFree(screen_info); + width = x11->width[0]; + height = x11->height[0]; + } else { +no_info: + screen_count = x11->screen_count; + res = malloc(screen_count * sizeof(*res)); + if (!res) + goto no_mem; + for (i = 0; i < screen_count; i++) { + res[i].width = x11->width[i]; + res[i].height = x11->height[i]; + /* No way to get screen coordinates, assume rtl order */ + res[i].x = width; + res[i].y = 0; + width += x11->width[i]; + if (x11->height[i] > height) + height = x11->height[i]; + } + } + + if (x11->debug) { + for (i = 0; i < screen_count; i++) + syslog(LOG_DEBUG, "Screen %d %dx%d%+d%+d", i, res[i].width, + res[i].height, res[i].x, res[i].y); + } + + udscs_write(x11->vdagentd, VDAGENTD_GUEST_XORG_RESOLUTION, width, height, + (uint8_t *)res, screen_count * sizeof(*res)); + free(res); + return; +no_mem: + syslog(LOG_ERR, "out of memory while trying to send resolutions, not sending resolutions."); +} diff --git a/src/vdagent/vdagent-x11.c b/src/vdagent/vdagent-x11.c new file mode 100644 index 0000000..da27602 --- /dev/null +++ b/src/vdagent/vdagent-x11.c @@ -0,0 +1,1356 @@ +/* vdagent-x11.c vdagent x11 code + + Copyright 2010-2011 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede <hdegoede@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Note: Our event loop is only called when there is data to be read from the + X11 socket. If events have arrived and have already been read by libX11 from + the socket triggered by other libX11 calls from this file, the select for + read in the main loop, won't see these and our event loop won't get called! + + Thus we must make sure that all queued events have been consumed, whenever + we return to the main loop. IOW all (externally callable) functions in this + file must end with calling XPending and consuming all queued events. + + Calling XPending when-ever we return to the mainloop also ensures any + pending writes are flushed. */ + +#include <glib.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <syslog.h> +#include <assert.h> +#include <unistd.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.h> +#include "vdagentd-proto.h" +#include "vdagent-x11.h" +#include "vdagent-x11-priv.h" + +/* Stupid X11 API, there goes our encapsulate all data in a struct design */ +int (*vdagent_x11_prev_error_handler)(Display *, XErrorEvent *); +int vdagent_x11_caught_error; + +static void vdagent_x11_handle_selection_notify(struct vdagent_x11 *x11, + XEvent *event, int incr); +static void vdagent_x11_handle_selection_request(struct vdagent_x11 *x11); +static void vdagent_x11_handle_targets_notify(struct vdagent_x11 *x11, + XEvent *event); +static void vdagent_x11_handle_property_delete_notify(struct vdagent_x11 *x11, + XEvent *del_event); +static void vdagent_x11_send_selection_notify(struct vdagent_x11 *x11, + Atom prop, struct vdagent_x11_selection_request *request); +static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11, + uint8_t selection, int new_owner); + +static const char *vdagent_x11_sel_to_str(uint8_t selection) { + switch (selection) { + case VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD: + return "clipboard"; + case VD_AGENT_CLIPBOARD_SELECTION_PRIMARY: + return "primary"; + case VD_AGENT_CLIPBOARD_SELECTION_SECONDARY: + return "secondary"; + default: + return "unknown"; + } +} + +static int vdagent_x11_debug_error_handler( + Display *display, XErrorEvent *error) +{ + abort(); +} + +/* With the clipboard we're sometimes dealing with Properties on another apps + Window. which can go away at any time. */ +static int vdagent_x11_ignore_bad_window_handler( + Display *display, XErrorEvent *error) +{ + if (error->error_code == BadWindow) + return 0; + + return vdagent_x11_prev_error_handler(display, error); +} + +void vdagent_x11_set_error_handler(struct vdagent_x11 *x11, + int (*handler)(Display *, XErrorEvent *)) +{ + XSync(x11->display, False); + vdagent_x11_caught_error = 0; + vdagent_x11_prev_error_handler = XSetErrorHandler(handler); +} + +int vdagent_x11_restore_error_handler(struct vdagent_x11 *x11) +{ + int error; + + XSync(x11->display, False); + XSetErrorHandler(vdagent_x11_prev_error_handler); + error = vdagent_x11_caught_error; + vdagent_x11_caught_error = 0; + + return error; +} + +static void vdagent_x11_get_wm_name(struct vdagent_x11 *x11) +{ + Atom type_ret; + int format_ret; + unsigned long len, remain; + unsigned char *data = NULL; + Window sup_window = None; + + /* XGetWindowProperty can throw a BadWindow error. One way we can trigger + this is when the display-manager (ie gdm) has set, and not cleared the + _NET_SUPPORTING_WM_CHECK property, and the window manager running in + the user session has not yet updated it to point to its window, so its + pointing to a non existing window. */ + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + + /* Get the window manager SUPPORTING_WM_CHECK window */ + if (XGetWindowProperty(x11->display, x11->root_window[0], + XInternAtom(x11->display, "_NET_SUPPORTING_WM_CHECK", False), 0, + LONG_MAX, False, XA_WINDOW, &type_ret, &format_ret, &len, + &remain, &data) == Success) { + if (type_ret == XA_WINDOW) + sup_window = *((Window *)data); + XFree(data); + } + if (sup_window == None && + XGetWindowProperty(x11->display, x11->root_window[0], + XInternAtom(x11->display, "_WIN_SUPPORTING_WM_CHECK", False), 0, + LONG_MAX, False, XA_CARDINAL, &type_ret, &format_ret, &len, + &remain, &data) == Success) { + if (type_ret == XA_CARDINAL) + sup_window = *((Window *)data); + XFree(data); + } + /* So that we can get the net_wm_name */ + if (sup_window != None) { + Atom utf8 = XInternAtom(x11->display, "UTF8_STRING", False); + if (XGetWindowProperty(x11->display, sup_window, + XInternAtom(x11->display, "_NET_WM_NAME", False), 0, + LONG_MAX, False, utf8, &type_ret, &format_ret, &len, + &remain, &data) == Success) { + if (type_ret == utf8) { + x11->net_wm_name = + g_strndup((char *)data, (format_ret / 8) * len); + } + XFree(data); + } + if (x11->net_wm_name == NULL && + XGetWindowProperty(x11->display, sup_window, + XInternAtom(x11->display, "_NET_WM_NAME", False), 0, + LONG_MAX, False, XA_STRING, &type_ret, &format_ret, &len, + &remain, &data) == Success) { + if (type_ret == XA_STRING) { + x11->net_wm_name = + g_strndup((char *)data, (format_ret / 8) * len); + } + XFree(data); + } + } + + vdagent_x11_restore_error_handler(x11); +} + +struct vdagent_x11 *vdagent_x11_create(struct udscs_connection *vdagentd, + int debug, int sync) +{ + struct vdagent_x11 *x11; + XWindowAttributes attrib; + int i, j, major, minor; + + x11 = calloc(1, sizeof(*x11)); + if (!x11) { + syslog(LOG_ERR, "out of memory allocating vdagent_x11 struct"); + return NULL; + } + + x11->vdagentd = vdagentd; + x11->debug = debug; + + x11->display = XOpenDisplay(NULL); + if (!x11->display) { + syslog(LOG_ERR, "could not connect to X-server"); + free(x11); + return NULL; + } + + x11->screen_count = ScreenCount(x11->display); + if (x11->screen_count > MAX_SCREENS) { + syslog(LOG_ERR, "Error too much screens: %d > %d", + x11->screen_count, MAX_SCREENS); + XCloseDisplay(x11->display); + free(x11); + return NULL; + } + + if (sync) { + XSetErrorHandler(vdagent_x11_debug_error_handler); + XSynchronize(x11->display, True); + } + + for (i = 0; i < x11->screen_count; i++) + x11->root_window[i] = RootWindow(x11->display, i); + x11->fd = ConnectionNumber(x11->display); + x11->clipboard_atom = XInternAtom(x11->display, "CLIPBOARD", False); + x11->clipboard_primary_atom = XInternAtom(x11->display, "PRIMARY", False); + x11->targets_atom = XInternAtom(x11->display, "TARGETS", False); + x11->incr_atom = XInternAtom(x11->display, "INCR", False); + x11->multiple_atom = XInternAtom(x11->display, "MULTIPLE", False); + x11->timestamp_atom = XInternAtom(x11->display, "TIMESTAMP", False); + for(i = 0; i < clipboard_format_count; i++) { + x11->clipboard_formats[i].type = clipboard_format_templates[i].type; + for(j = 0; clipboard_format_templates[i].atom_names[j]; j++) { + x11->clipboard_formats[i].atoms[j] = + XInternAtom(x11->display, + clipboard_format_templates[i].atom_names[j], + False); + } + x11->clipboard_formats[i].atom_count = j; + } + + /* We should not store properties (for selections) on the root window */ + x11->selection_window = XCreateSimpleWindow(x11->display, x11->root_window[0], + 0, 0, 1, 1, 0, 0, 0); + if (x11->debug) + syslog(LOG_DEBUG, "Selection window: %u", (int)x11->selection_window); + + vdagent_x11_randr_init(x11); + + if (XFixesQueryExtension(x11->display, &x11->xfixes_event_base, &i) && + XFixesQueryVersion(x11->display, &major, &minor) && major >= 1) { + x11->has_xfixes = 1; + XFixesSelectSelectionInput(x11->display, x11->root_window[0], + x11->clipboard_atom, + XFixesSetSelectionOwnerNotifyMask| + XFixesSelectionWindowDestroyNotifyMask| + XFixesSelectionClientCloseNotifyMask); + XFixesSelectSelectionInput(x11->display, x11->root_window[0], + x11->clipboard_primary_atom, + XFixesSetSelectionOwnerNotifyMask| + XFixesSelectionWindowDestroyNotifyMask| + XFixesSelectionClientCloseNotifyMask); + } else + syslog(LOG_ERR, "no xfixes, no guest -> client copy paste support"); + + x11->max_prop_size = XExtendedMaxRequestSize(x11->display); + if (x11->max_prop_size) { + x11->max_prop_size -= 100; + } else { + x11->max_prop_size = XMaxRequestSize(x11->display) - 100; + } + /* Be a good X11 citizen and maximize the amount of data we send at once */ + if (x11->max_prop_size > 262144) + x11->max_prop_size = 262144; + + for (i = 0; i < x11->screen_count; i++) { + /* Catch resolution changes */ + XSelectInput(x11->display, x11->root_window[i], StructureNotifyMask); + + /* Get the current resolution */ + XGetWindowAttributes(x11->display, x11->root_window[i], &attrib); + x11->width[i] = attrib.width; + x11->height[i] = attrib.height; + } + vdagent_x11_send_daemon_guest_xorg_res(x11, 1); + + /* Get net_wm_name, since we are started at the same time as the wm, + sometimes we need to wait a bit for it to show up. */ + i = 10; + vdagent_x11_get_wm_name(x11); + while (x11->net_wm_name == NULL && --i > 0) { + usleep(100000); + vdagent_x11_get_wm_name(x11); + } + if (x11->debug && x11->net_wm_name) + syslog(LOG_DEBUG, "net_wm_name: \"%s\", has icons: %d", + x11->net_wm_name, vdagent_x11_has_icons_on_desktop(x11)); + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); + + return x11; +} + +void vdagent_x11_destroy(struct vdagent_x11 *x11, int vdagentd_disconnected) +{ + uint8_t sel; + + if (!x11) + return; + + if (vdagentd_disconnected) + x11->vdagentd = NULL; + + for (sel = 0; sel < VD_AGENT_CLIPBOARD_SELECTION_SECONDARY; ++sel) { + vdagent_x11_set_clipboard_owner(x11, sel, owner_none); + } + + XCloseDisplay(x11->display); + g_free(x11->net_wm_name); + free(x11->randr.failed_conf); + free(x11); +} + +int vdagent_x11_get_fd(struct vdagent_x11 *x11) +{ + return x11->fd; +} + +static void vdagent_x11_next_selection_request(struct vdagent_x11 *x11) +{ + struct vdagent_x11_selection_request *selection_request; + selection_request = x11->selection_req; + x11->selection_req = selection_request->next; + free(selection_request); +} + +static void vdagent_x11_next_conversion_request(struct vdagent_x11 *x11) +{ + struct vdagent_x11_conversion_request *conversion_req; + conversion_req = x11->conversion_req; + x11->conversion_req = conversion_req->next; + free(conversion_req); +} + +static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11, + uint8_t selection, int new_owner) +{ + struct vdagent_x11_selection_request *prev_sel, *curr_sel, *next_sel; + struct vdagent_x11_conversion_request *prev_conv, *curr_conv, *next_conv; + int once; + + /* Clear pending requests and clipboard data */ + once = 1; + prev_sel = NULL; + next_sel = x11->selection_req; + while (next_sel) { + curr_sel = next_sel; + next_sel = curr_sel->next; + if (curr_sel->selection == selection) { + if (once) { + SELPRINTF("selection requests pending on clipboard ownership " + "change, clearing"); + once = 0; + } + vdagent_x11_send_selection_notify(x11, None, curr_sel); + if (curr_sel == x11->selection_req) { + x11->selection_req = next_sel; + free(x11->selection_req_data); + x11->selection_req_data = NULL; + x11->selection_req_data_pos = 0; + x11->selection_req_data_size = 0; + x11->selection_req_atom = None; + } else { + prev_sel->next = next_sel; + } + free(curr_sel); + } else { + prev_sel = curr_sel; + } + } + + once = 1; + prev_conv = NULL; + next_conv = x11->conversion_req; + while (next_conv) { + curr_conv = next_conv; + next_conv = curr_conv->next; + if (curr_conv->selection == selection) { + if (once) { + SELPRINTF("client clipboard request pending on clipboard " + "ownership change, clearing"); + once = 0; + } + if (x11->vdagentd) + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA, selection, + VD_AGENT_CLIPBOARD_NONE, NULL, 0); + if (curr_conv == x11->conversion_req) { + x11->conversion_req = next_conv; + x11->clipboard_data_size = 0; + x11->expect_property_notify = 0; + } else { + prev_conv->next = next_conv; + } + free(curr_conv); + } else { + prev_conv = curr_conv; + } + } + + if (new_owner == owner_none) { + /* When going from owner_guest to owner_none we need to send a + clipboard release message to the client */ + if (x11->clipboard_owner[selection] == owner_guest && x11->vdagentd) { + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_RELEASE, selection, + 0, NULL, 0); + } + x11->clipboard_type_count[selection] = 0; + } + x11->clipboard_owner[selection] = new_owner; +} + +static int vdagent_x11_get_clipboard_atom(struct vdagent_x11 *x11, uint8_t selection, Atom* clipboard) +{ + if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) { + *clipboard = x11->clipboard_atom; + } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) { + *clipboard = x11->clipboard_primary_atom; + } else { + syslog(LOG_ERR, "get_clipboard_atom: unknown selection"); + return -1; + } + + return 0; +} + +static int vdagent_x11_get_clipboard_selection(struct vdagent_x11 *x11, + XEvent *event, uint8_t *selection) +{ + Atom atom; + + if (event->type == x11->xfixes_event_base) { + XFixesSelectionNotifyEvent *xfev = (XFixesSelectionNotifyEvent *)event; + atom = xfev->selection; + } else if (event->type == SelectionNotify) { + atom = event->xselection.selection; + } else if (event->type == SelectionRequest) { + atom = event->xselectionrequest.selection; + } else { + syslog(LOG_ERR, "get_clipboard_selection: unknown event type"); + return -1; + } + + if (atom == x11->clipboard_atom) { + *selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; + } else if (atom == x11->clipboard_primary_atom) { + *selection = VD_AGENT_CLIPBOARD_SELECTION_PRIMARY; + } else { + syslog(LOG_ERR, "get_clipboard_selection: unknown selection"); + return -1; + } + + return 0; +} + +static void vdagent_x11_handle_event(struct vdagent_x11 *x11, XEvent event) +{ + int i, handled = 0; + uint8_t selection; + + if (event.type == x11->xfixes_event_base) { + union { + XEvent ev; + XFixesSelectionNotifyEvent xfev; + } ev; + + if (vdagent_x11_get_clipboard_selection(x11, &event, &selection)) { + return; + } + + ev.ev = event; + switch (ev.xfev.subtype) { + case XFixesSetSelectionOwnerNotify: + break; + /* Treat ... as a SelectionOwnerNotify None */ + case XFixesSelectionWindowDestroyNotify: + case XFixesSelectionClientCloseNotify: + ev.xfev.owner = None; + break; + default: + VSELPRINTF("unexpected xfix event subtype %d window %d", + (int)ev.xfev.subtype, (int)event.xany.window); + return; + } + VSELPRINTF("New selection owner: %u", (unsigned int)ev.xfev.owner); + + /* Ignore becoming the owner ourselves */ + if (ev.xfev.owner == x11->selection_window) + return; + + /* If the clipboard owner is changed we no longer own it */ + vdagent_x11_set_clipboard_owner(x11, selection, owner_none); + + if (ev.xfev.owner == None) + return; + + /* Request the supported targets from the new owner */ + XConvertSelection(x11->display, ev.xfev.selection, x11->targets_atom, + x11->targets_atom, x11->selection_window, + CurrentTime); + x11->expected_targets_notifies[selection]++; + return; + } + + if (vdagent_x11_randr_handle_event(x11, event)) + return; + + switch (event.type) { + case ConfigureNotify: + for (i = 0; i < x11->screen_count; i++) + if (event.xconfigure.window == x11->root_window[i]) + break; + if (i == x11->screen_count) + break; + + handled = 1; + vdagent_x11_randr_handle_root_size_change(x11, i, + event.xconfigure.width, event.xconfigure.height); + break; + case MappingNotify: + /* These are uninteresting */ + handled = 1; + break; + case SelectionNotify: + if (event.xselection.target == x11->targets_atom) + vdagent_x11_handle_targets_notify(x11, &event); + else + vdagent_x11_handle_selection_notify(x11, &event, 0); + + handled = 1; + break; + case PropertyNotify: + if (x11->expect_property_notify && + event.xproperty.state == PropertyNewValue) { + vdagent_x11_handle_selection_notify(x11, &event, 1); + } + if (x11->selection_req_data && + event.xproperty.state == PropertyDelete) { + vdagent_x11_handle_property_delete_notify(x11, &event); + } + /* Always mark as handled, since we cannot unselect input for property + notifications once we are done with handling the incr transfer. */ + handled = 1; + break; + case SelectionClear: + /* Do nothing the clipboard ownership will get updated through + the XFixesSetSelectionOwnerNotify event */ + handled = 1; + break; + case SelectionRequest: { + struct vdagent_x11_selection_request *req, *new_req; + + if (vdagent_x11_get_clipboard_selection(x11, &event, &selection)) { + return; + } + + new_req = malloc(sizeof(*new_req)); + if (!new_req) { + SELPRINTF("out of memory on SelectionRequest, ignoring."); + break; + } + + handled = 1; + + new_req->event = event; + new_req->selection = selection; + new_req->next = NULL; + + if (!x11->selection_req) { + x11->selection_req = new_req; + vdagent_x11_handle_selection_request(x11); + break; + } + + /* maybe we should limit the selection_request stack depth ? */ + req = x11->selection_req; + while (req->next) + req = req->next; + + req->next = new_req; + break; + } + } + if (!handled && x11->debug) + syslog(LOG_DEBUG, "unhandled x11 event, type %d, window %d", + (int)event.type, (int)event.xany.window); +} + +void vdagent_x11_do_read(struct vdagent_x11 *x11) +{ + XEvent event; + + while (XPending(x11->display)) { + XNextEvent(x11->display, &event); + vdagent_x11_handle_event(x11, event); + } +} + +static const char *vdagent_x11_get_atom_name(struct vdagent_x11 *x11, Atom a) +{ + if (a == None) + return "None"; + + return XGetAtomName(x11->display, a); +} + +static int vdagent_x11_get_selection(struct vdagent_x11 *x11, XEvent *event, + uint8_t selection, Atom type, Atom prop, int format, + unsigned char **data_ret, int incr) +{ + Bool del = incr ? True: False; + Atom type_ret; + int format_ret, ret_val = -1; + unsigned long len, remain; + unsigned char *data = NULL; + + *data_ret = NULL; + + if (!incr) { + if (event->xselection.property == None) { + VSELPRINTF("XConvertSelection refused by clipboard owner"); + goto exit; + } + + if (event->xselection.requestor != x11->selection_window || + event->xselection.property != prop) { + SELPRINTF("SelectionNotify parameters mismatch"); + goto exit; + } + } + + if (XGetWindowProperty(x11->display, x11->selection_window, prop, 0, + LONG_MAX, del, type, &type_ret, &format_ret, &len, + &remain, &data) != Success) { + SELPRINTF("XGetWindowProperty failed"); + goto exit; + } + + if (!incr && prop != x11->targets_atom) { + if (type_ret == x11->incr_atom) { + int prop_min_size = *(uint32_t*)data; + + if (x11->expect_property_notify) { + SELPRINTF("received an incr SelectionNotify while " + "still reading another incr property"); + goto exit; + } + + if (x11->clipboard_data_space < prop_min_size) { + free(x11->clipboard_data); + x11->clipboard_data = malloc(prop_min_size); + if (!x11->clipboard_data) { + SELPRINTF("out of memory allocating clipboard buffer"); + x11->clipboard_data_space = 0; + goto exit; + } + x11->clipboard_data_space = prop_min_size; + } + x11->expect_property_notify = 1; + XSelectInput(x11->display, x11->selection_window, + PropertyChangeMask); + XDeleteProperty(x11->display, x11->selection_window, prop); + XFree(data); + return 0; /* Wait for more data */ + } + XDeleteProperty(x11->display, x11->selection_window, prop); + } + + if (type_ret != type) { + SELPRINTF("expected property type: %s, got: %s", + vdagent_x11_get_atom_name(x11, type), + vdagent_x11_get_atom_name(x11, type_ret)); + goto exit; + } + + if (format_ret != format) { + SELPRINTF("expected %d bit format, got %d bits", format, format_ret); + goto exit; + } + + /* Convert len to bytes */ + switch(format) { + case 8: + break; + case 16: + len *= sizeof(short); + break; + case 32: + len *= sizeof(long); + break; + } + + if (incr) { + if (len) { + if (x11->clipboard_data_size + len > x11->clipboard_data_space) { + void *old_clipboard_data = x11->clipboard_data; + + x11->clipboard_data_space = x11->clipboard_data_size + len; + x11->clipboard_data = realloc(x11->clipboard_data, + x11->clipboard_data_space); + if (!x11->clipboard_data) { + SELPRINTF("out of memory allocating clipboard buffer"); + x11->clipboard_data_space = 0; + free(old_clipboard_data); + goto exit; + } + } + memcpy(x11->clipboard_data + x11->clipboard_data_size, data, len); + x11->clipboard_data_size += len; + VSELPRINTF("Appended %ld bytes to buffer", len); + XFree(data); + return 0; /* Wait for more data */ + } + len = x11->clipboard_data_size; + *data_ret = x11->clipboard_data; + } else + *data_ret = data; + + if (len > 0) { + ret_val = len; + } else { + SELPRINTF("property contains no data (zero length)"); + *data_ret = NULL; + } + +exit: + if ((incr || ret_val == -1) && data) + XFree(data); + + if (incr) { + x11->clipboard_data_size = 0; + x11->expect_property_notify = 0; + } + + return ret_val; +} + +static void vdagent_x11_get_selection_free(struct vdagent_x11 *x11, + unsigned char *data, int incr) +{ + if (incr) { + /* If the clipboard has grown large return the memory to the system */ + if (x11->clipboard_data_space > 512 * 1024) { + free(x11->clipboard_data); + x11->clipboard_data = NULL; + x11->clipboard_data_space = 0; + } + } else if (data) + XFree(data); +} + +static uint32_t vdagent_x11_target_to_type(struct vdagent_x11 *x11, + uint8_t selection, Atom target) +{ + int i, j; + + for (i = 0; i < clipboard_format_count; i++) { + for (j = 0; j < x11->clipboard_formats[i].atom_count; j++) { + if (x11->clipboard_formats[i].atoms[j] == target) { + return x11->clipboard_formats[i].type; + } + } + } + + VSELPRINTF("unexpected selection type %s", + vdagent_x11_get_atom_name(x11, target)); + return VD_AGENT_CLIPBOARD_NONE; +} + +static Atom vdagent_x11_type_to_target(struct vdagent_x11 *x11, + uint8_t selection, uint32_t type) +{ + int i; + + for (i = 0; i < x11->clipboard_type_count[selection]; i++) { + if (x11->clipboard_agent_types[selection][i] == type) { + return x11->clipboard_x11_targets[selection][i]; + } + } + SELPRINTF("client requested unavailable type %u", type); + return None; +} + +static void vdagent_x11_handle_conversion_request(struct vdagent_x11 *x11) +{ + Atom clip = None; + + if (!x11->conversion_req) { + return; + } + + vdagent_x11_get_clipboard_atom(x11, x11->conversion_req->selection, &clip); + XConvertSelection(x11->display, clip, x11->conversion_req->target, + clip, x11->selection_window, CurrentTime); +} + +static void vdagent_x11_handle_selection_notify(struct vdagent_x11 *x11, + XEvent *event, int incr) +{ + int len = 0; + unsigned char *data = NULL; + uint32_t type; + uint8_t selection = -1; + Atom clip = None; + + if (!x11->conversion_req) { + syslog(LOG_ERR, "SelectionNotify received without a target"); + return; + } + vdagent_x11_get_clipboard_atom(x11, x11->conversion_req->selection, &clip); + + if (incr) { + if (event->xproperty.atom != clip || + event->xproperty.window != x11->selection_window) { + return; + } + } else { + if (vdagent_x11_get_clipboard_selection(x11, event, &selection)) { + len = -1; + } else if (selection != x11->conversion_req->selection) { + SELPRINTF("Requested data for selection %d got %d", + (int)x11->conversion_req->selection, (int)selection); + len = -1; + } + if (event->xselection.target != x11->conversion_req->target && + event->xselection.target != x11->incr_atom) { + SELPRINTF("Requested %s target got %s", + vdagent_x11_get_atom_name(x11, x11->conversion_req->target), + vdagent_x11_get_atom_name(x11, event->xselection.target)); + len = -1; + } + } + + selection = x11->conversion_req->selection; + type = vdagent_x11_target_to_type(x11, selection, + x11->conversion_req->target); + if (type == VD_AGENT_CLIPBOARD_NONE) + SELPRINTF("internal error conversion_req has bad target %s", + vdagent_x11_get_atom_name(x11, x11->conversion_req->target)); + if (len == 0) { /* No errors so far */ + len = vdagent_x11_get_selection(x11, event, selection, + x11->conversion_req->target, + clip, 8, &data, incr); + if (len == 0) { /* waiting for more data? */ + return; + } + } + if (len == -1) { + type = VD_AGENT_CLIPBOARD_NONE; + len = 0; + } + + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA, selection, type, + data, len); + vdagent_x11_get_selection_free(x11, data, incr); + + vdagent_x11_next_conversion_request(x11); + vdagent_x11_handle_conversion_request(x11); +} + +static Atom atom_lists_overlap(Atom *atoms1, Atom *atoms2, int l1, int l2) +{ + int i, j; + + for (i = 0; i < l1; i++) + for (j = 0; j < l2; j++) + if (atoms1[i] == atoms2[j]) + return atoms1[i]; + + return 0; +} + +static void vdagent_x11_print_targets(struct vdagent_x11 *x11, + uint8_t selection, const char *action, Atom *atoms, int c) +{ + int i; + VSELPRINTF("%s %d targets:", action, c); + for (i = 0; i < c; i++) + VSELPRINTF("%s", vdagent_x11_get_atom_name(x11, atoms[i])); +} + +static void vdagent_x11_handle_targets_notify(struct vdagent_x11 *x11, + XEvent *event) +{ + int i, len; + Atom atom, *atoms = NULL; + uint8_t selection; + int *type_count; + + if (vdagent_x11_get_clipboard_selection(x11, event, &selection)) { + return; + } + + if (!x11->expected_targets_notifies[selection]) { + SELPRINTF("unexpected selection notify TARGETS"); + return; + } + + x11->expected_targets_notifies[selection]--; + + /* If we have more targets_notifies pending, ignore this one, we + are only interested in the targets list of the current owner + (which is the last one we've requested a targets list from) */ + if (x11->expected_targets_notifies[selection]) { + return; + } + + len = vdagent_x11_get_selection(x11, event, selection, + XA_ATOM, x11->targets_atom, 32, + (unsigned char **)&atoms, 0); + if (len == 0 || len == -1) /* waiting for more data or error? */ + return; + + /* bytes -> atoms */ + len /= sizeof(Atom); + vdagent_x11_print_targets(x11, selection, "received", atoms, len); + + type_count = &x11->clipboard_type_count[selection]; + *type_count = 0; + for (i = 0; i < clipboard_format_count; i++) { + atom = atom_lists_overlap(x11->clipboard_formats[i].atoms, atoms, + x11->clipboard_formats[i].atom_count, len); + if (atom) { + x11->clipboard_agent_types[selection][*type_count] = + x11->clipboard_formats[i].type; + x11->clipboard_x11_targets[selection][*type_count] = atom; + (*type_count)++; + if (*type_count == + sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t)) { + SELPRINTF("handle_targets_notify: too many types"); + break; + } + } + } + + if (*type_count) { + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_GRAB, selection, 0, + (uint8_t *)x11->clipboard_agent_types[selection], + *type_count * sizeof(uint32_t)); + vdagent_x11_set_clipboard_owner(x11, selection, owner_guest); + } + + vdagent_x11_get_selection_free(x11, (unsigned char *)atoms, 0); +} + +static void vdagent_x11_send_selection_notify(struct vdagent_x11 *x11, + Atom prop, struct vdagent_x11_selection_request *request) +{ + XEvent res, *event; + + if (request) { + event = &request->event; + } else { + event = &x11->selection_req->event; + } + + res.xselection.property = prop; + res.xselection.type = SelectionNotify; + res.xselection.display = event->xselectionrequest.display; + res.xselection.requestor = event->xselectionrequest.requestor; + res.xselection.selection = event->xselectionrequest.selection; + res.xselection.target = event->xselectionrequest.target; + res.xselection.time = event->xselectionrequest.time; + + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + XSendEvent(x11->display, event->xselectionrequest.requestor, 0, 0, &res); + vdagent_x11_restore_error_handler(x11); + + if (!request) { + vdagent_x11_next_selection_request(x11); + vdagent_x11_handle_selection_request(x11); + } +} + +static void vdagent_x11_send_targets(struct vdagent_x11 *x11, + uint8_t selection, XEvent *event) +{ + Atom prop, targets[256] = { x11->targets_atom, }; + int i, j, k, target_count = 1; + + for (i = 0; i < x11->clipboard_type_count[selection]; i++) { + for (j = 0; j < clipboard_format_count; j++) { + if (x11->clipboard_formats[j].type != + x11->clipboard_agent_types[selection][i]) + continue; + + for (k = 0; k < x11->clipboard_formats[j].atom_count; k++) { + targets[target_count] = x11->clipboard_formats[j].atoms[k]; + target_count++; + if (target_count == sizeof(targets)/sizeof(Atom)) { + SELPRINTF("send_targets: too many targets"); + goto exit_loop; + } + } + } + } +exit_loop: + + prop = event->xselectionrequest.property; + if (prop == None) + prop = event->xselectionrequest.target; + + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + XChangeProperty(x11->display, event->xselectionrequest.requestor, prop, + XA_ATOM, 32, PropModeReplace, (unsigned char *)&targets, + target_count); + if (vdagent_x11_restore_error_handler(x11) == 0) { + vdagent_x11_print_targets(x11, selection, "sent", + targets, target_count); + vdagent_x11_send_selection_notify(x11, prop, NULL); + } else + SELPRINTF("send_targets: Failed to sent, requestor window gone"); +} + +static void vdagent_x11_handle_selection_request(struct vdagent_x11 *x11) +{ + XEvent *event; + uint32_t type = VD_AGENT_CLIPBOARD_NONE; + uint8_t selection; + + if (!x11->selection_req) + return; + + event = &x11->selection_req->event; + selection = x11->selection_req->selection; + + if (x11->clipboard_owner[selection] != owner_client) { + SELPRINTF("received selection request event for target %s, " + "while not owning client clipboard", + vdagent_x11_get_atom_name(x11, event->xselectionrequest.target)); + vdagent_x11_send_selection_notify(x11, None, NULL); + return; + } + + if (event->xselectionrequest.target == x11->multiple_atom) { + SELPRINTF("multiple target not supported"); + vdagent_x11_send_selection_notify(x11, None, NULL); + return; + } + + if (event->xselectionrequest.target == x11->timestamp_atom) { + /* TODO: use more accurate selection time */ + guint32 timestamp = event->xselectionrequest.time; + + XChangeProperty(x11->display, event->xselectionrequest.requestor, + event->xselectionrequest.property, + event->xselectionrequest.target, 32, PropModeReplace, + (guint8*)×tamp, 1); + vdagent_x11_send_selection_notify(x11, + event->xselectionrequest.property, NULL); + return; + } + + + if (event->xselectionrequest.target == x11->targets_atom) { + vdagent_x11_send_targets(x11, selection, event); + return; + } + + type = vdagent_x11_target_to_type(x11, selection, + event->xselectionrequest.target); + if (type == VD_AGENT_CLIPBOARD_NONE) { + VSELPRINTF("guest app requested a non-advertised target"); + vdagent_x11_send_selection_notify(x11, None, NULL); + return; + } + + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_REQUEST, selection, type, + NULL, 0); +} + +static void vdagent_x11_handle_property_delete_notify(struct vdagent_x11 *x11, + XEvent *del_event) +{ + XEvent *sel_event; + int len; + uint8_t selection; + + assert(x11->selection_req); + sel_event = &x11->selection_req->event; + selection = x11->selection_req->selection; + if (del_event->xproperty.window != sel_event->xselectionrequest.requestor + || del_event->xproperty.atom != x11->selection_req_atom) { + return; + } + + len = x11->selection_req_data_size - x11->selection_req_data_pos; + if (len > x11->max_prop_size) { + len = x11->max_prop_size; + } + + if (len) { + VSELPRINTF("Sending %d-%d/%d bytes of clipboard data", + x11->selection_req_data_pos, + x11->selection_req_data_pos + len - 1, + x11->selection_req_data_size); + } else { + VSELPRINTF("Ending incr send of clipboard data"); + } + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + XChangeProperty(x11->display, sel_event->xselectionrequest.requestor, + x11->selection_req_atom, + sel_event->xselectionrequest.target, 8, PropModeReplace, + x11->selection_req_data + x11->selection_req_data_pos, + len); + if (vdagent_x11_restore_error_handler(x11)) { + SELPRINTF("incr sent failed, requestor window gone"); + len = 0; + } + + x11->selection_req_data_pos += len; + + /* Note we must explictly send a 0 sized XChangeProperty to signal the + incr transfer is done. Hence we do not check if we've send all data + but instead check we've send the final 0 sized XChangeProperty. */ + if (len == 0) { + free(x11->selection_req_data); + x11->selection_req_data = NULL; + x11->selection_req_data_pos = 0; + x11->selection_req_data_size = 0; + x11->selection_req_atom = None; + vdagent_x11_next_selection_request(x11); + vdagent_x11_handle_selection_request(x11); + } +} + +void vdagent_x11_clipboard_request(struct vdagent_x11 *x11, + uint8_t selection, uint32_t type) +{ + Atom target, clip; + struct vdagent_x11_conversion_request *req, *new_req; + + /* We don't use clip here, but we call get_clipboard_atom to verify + selection is valid */ + if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) { + goto none; + } + + if (x11->clipboard_owner[selection] != owner_guest) { + SELPRINTF("received clipboard req while not owning guest clipboard"); + goto none; + } + + target = vdagent_x11_type_to_target(x11, selection, type); + if (target == None) { + goto none; + } + + new_req = malloc(sizeof(*new_req)); + if (!new_req) { + SELPRINTF("out of memory on client clipboard request, ignoring."); + return; + } + + new_req->target = target; + new_req->selection = selection; + new_req->next = NULL; + + if (!x11->conversion_req) { + x11->conversion_req = new_req; + vdagent_x11_handle_conversion_request(x11); + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); + return; + } + + /* maybe we should limit the conversion_request stack depth ? */ + req = x11->conversion_req; + while (req->next) + req = req->next; + + req->next = new_req; + return; + +none: + udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA, + selection, VD_AGENT_CLIPBOARD_NONE, NULL, 0); +} + +void vdagent_x11_clipboard_grab(struct vdagent_x11 *x11, uint8_t selection, + uint32_t *types, uint32_t type_count) +{ + Atom clip = None; + + if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) { + return; + } + + if (type_count > sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t)) { + SELPRINTF("x11_clipboard_grab: too many types"); + type_count = sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t); + } + + memcpy(x11->clipboard_agent_types[selection], types, + type_count * sizeof(uint32_t)); + x11->clipboard_type_count[selection] = type_count; + + XSetSelectionOwner(x11->display, clip, + x11->selection_window, CurrentTime); + vdagent_x11_set_clipboard_owner(x11, selection, owner_client); + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); +} + +void vdagent_x11_clipboard_data(struct vdagent_x11 *x11, uint8_t selection, + uint32_t type, uint8_t *data, uint32_t size) +{ + Atom prop; + XEvent *event; + uint32_t type_from_event; + + if (x11->selection_req_data) { + if (type || size) { + SELPRINTF("received clipboard data while still sending" + " data from previous request, ignoring"); + } + free(data); + return; + } + + if (!x11->selection_req) { + if (type || size) { + SELPRINTF("received clipboard data without an " + "outstanding selection request, ignoring"); + } + free(data); + return; + } + + event = &x11->selection_req->event; + type_from_event = vdagent_x11_target_to_type(x11, + x11->selection_req->selection, + event->xselectionrequest.target); + if (type_from_event != type || + selection != x11->selection_req->selection) { + if (selection != x11->selection_req->selection) { + SELPRINTF("expecting data for selection %d got %d", + (int)x11->selection_req->selection, (int)selection); + } + if (type_from_event != type) { + SELPRINTF("expecting type %u clipboard data got %u", + type_from_event, type); + } + vdagent_x11_send_selection_notify(x11, None, NULL); + free(data); + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); + return; + } + + prop = event->xselectionrequest.property; + if (prop == None) + prop = event->xselectionrequest.target; + + if (size > x11->max_prop_size) { + unsigned long len = size; + VSELPRINTF("Starting incr send of clipboard data"); + + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + XSelectInput(x11->display, event->xselectionrequest.requestor, + PropertyChangeMask); + XChangeProperty(x11->display, event->xselectionrequest.requestor, prop, + x11->incr_atom, 32, PropModeReplace, + (unsigned char*)&len, 1); + if (vdagent_x11_restore_error_handler(x11) == 0) { + x11->selection_req_data = data; + x11->selection_req_data_pos = 0; + x11->selection_req_data_size = size; + x11->selection_req_atom = prop; + vdagent_x11_send_selection_notify(x11, prop, x11->selection_req); + } else { + SELPRINTF("clipboard data sent failed, requestor window gone"); + free(data); + } + } else { + vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler); + XChangeProperty(x11->display, event->xselectionrequest.requestor, prop, + event->xselectionrequest.target, 8, PropModeReplace, + data, size); + if (vdagent_x11_restore_error_handler(x11) == 0) + vdagent_x11_send_selection_notify(x11, prop, NULL); + else + SELPRINTF("clipboard data sent failed, requestor window gone"); + + free(data); + } + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); +} + +void vdagent_x11_clipboard_release(struct vdagent_x11 *x11, uint8_t selection) +{ + XEvent event; + Atom clip = None; + + if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) { + return; + } + + if (x11->clipboard_owner[selection] != owner_client) { + VSELPRINTF("received release while not owning client clipboard"); + return; + } + + XSetSelectionOwner(x11->display, clip, None, CurrentTime); + /* Make sure we process the XFixesSetSelectionOwnerNotify event caused + by this, so we don't end up changing the clipboard owner to none, after + it has already been re-owned because this event is still pending. */ + XSync(x11->display, False); + while (XCheckTypedEvent(x11->display, x11->xfixes_event_base, + &event)) + vdagent_x11_handle_event(x11, event); + + /* Note no need to do a set_clipboard_owner(owner_none) here, as that is + already done by processing the XFixesSetSelectionOwnerNotify event. */ + + /* Flush output buffers and consume any pending events */ + vdagent_x11_do_read(x11); +} + +void vdagent_x11_client_disconnected(struct vdagent_x11 *x11) +{ + int sel; + + for (sel = 0; sel < VD_AGENT_CLIPBOARD_SELECTION_SECONDARY; sel++) { + if (x11->clipboard_owner[sel] == owner_client) + vdagent_x11_clipboard_release(x11, sel); + } +} + +/* Function used to determine the default location to save file-xfers, + xdg desktop dir or xdg download dir. We error on the save side and use a + whitelist approach, so any unknown desktops will end up with saving + file-xfers to the xdg download dir, and opening the xdg download dir with + xdg-open when the file-xfer completes. */ +int vdagent_x11_has_icons_on_desktop(struct vdagent_x11 *x11) +{ + const char * const wms_with_icons_on_desktop[] = { + "Metacity", /* GNOME-2 or GNOME-3 fallback */ + "Xfwm4", /* XFCE */ + "Marco", /* Mate */ + NULL + }; + int i; + + if (x11->net_wm_name) + for (i = 0; wms_with_icons_on_desktop[i]; i++) + if (!strcmp(x11->net_wm_name, wms_with_icons_on_desktop[i])) + return 1; + + return 0; +} diff --git a/src/vdagent/vdagent-x11.h b/src/vdagent/vdagent-x11.h new file mode 100644 index 0000000..e67701e --- /dev/null +++ b/src/vdagent/vdagent-x11.h @@ -0,0 +1,53 @@ +/* vdagent-x11.h vdagent x11 code header file + + Copyright 2010 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede <hdegoede@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __VDAGENT_X11_H +#define __VDAGENT_X11_H + +#include <stdio.h> +#include <spice/vd_agent.h> +#include "udscs.h" + +struct vdagent_x11; + +struct vdagent_x11 *vdagent_x11_create(struct udscs_connection *vdagentd, + int debug, int sync); +void vdagent_x11_destroy(struct vdagent_x11 *x11, int vdagentd_disconnected); + +int vdagent_x11_get_fd(struct vdagent_x11 *x11); +void vdagent_x11_do_read(struct vdagent_x11 *x11); + +void vdagent_x11_set_monitor_config(struct vdagent_x11 *x11, + VDAgentMonitorsConfig *mon_config, int fallback); + +void vdagent_x11_clipboard_grab(struct vdagent_x11 *x11, uint8_t selection, + uint32_t *types, uint32_t type_count); +void vdagent_x11_clipboard_request(struct vdagent_x11 *x11, + uint8_t selection, uint32_t type); +void vdagent_x11_clipboard_data(struct vdagent_x11 *x11, uint8_t selection, + uint32_t type, uint8_t *data, uint32_t size); +void vdagent_x11_clipboard_release(struct vdagent_x11 *x11, uint8_t selection); + +void vdagent_x11_client_disconnected(struct vdagent_x11 *x11); + +int vdagent_x11_has_icons_on_desktop(struct vdagent_x11 *x11); + +#endif diff --git a/src/vdagent/vdagent.c b/src/vdagent/vdagent.c new file mode 100644 index 0000000..a3cdd9b --- /dev/null +++ b/src/vdagent/vdagent.c @@ -0,0 +1,387 @@ +/* vdagent.c xorg-client to vdagentd (daemon). + + Copyright 2010-2013 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede <hdegoede@redhat.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <spice/vd_agent.h> +#include <glib.h> +#include <poll.h> + +#include "udscs.h" +#include "vdagentd-proto.h" +#include "vdagentd-proto-strings.h" +#include "vdagent-audio.h" +#include "vdagent-x11.h" +#include "vdagent-file-xfers.h" + +static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0"; +static const char *vdagentd_socket = VDAGENTD_SOCKET; +static int debug = 0; +static const char *fx_dir = NULL; +static int fx_open_dir = -1; +static struct vdagent_x11 *x11 = NULL; +static struct vdagent_file_xfers *vdagent_file_xfers = NULL; +static struct udscs_connection *client = NULL; +static int quit = 0; +static int version_mismatch = 0; + +static void daemon_read_complete(struct udscs_connection **connp, + struct udscs_message_header *header, uint8_t *data) +{ + switch (header->type) { + case VDAGENTD_MONITORS_CONFIG: + vdagent_x11_set_monitor_config(x11, (VDAgentMonitorsConfig *)data, 0); + free(data); + break; + case VDAGENTD_CLIPBOARD_REQUEST: + vdagent_x11_clipboard_request(x11, header->arg1, header->arg2); + free(data); + break; + case VDAGENTD_CLIPBOARD_GRAB: + vdagent_x11_clipboard_grab(x11, header->arg1, (uint32_t *)data, + header->size / sizeof(uint32_t)); + free(data); + break; + case VDAGENTD_CLIPBOARD_DATA: + vdagent_x11_clipboard_data(x11, header->arg1, header->arg2, + data, header->size); + /* vdagent_x11_clipboard_data takes ownership of the data (or frees + it immediately) */ + break; + case VDAGENTD_CLIPBOARD_RELEASE: + vdagent_x11_clipboard_release(x11, header->arg1); + free(data); + break; + case VDAGENTD_VERSION: + if (strcmp((char *)data, VERSION) != 0) { + syslog(LOG_INFO, "vdagentd version mismatch: got %s expected %s", + data, VERSION); + udscs_destroy_connection(connp); + version_mismatch = 1; + } + break; + case VDAGENTD_FILE_XFER_START: + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_start(vdagent_file_xfers, + (VDAgentFileXferStartMessage *)data); + } else { + vdagent_file_xfers_error(*connp, + ((VDAgentFileXferStartMessage *)data)->id); + } + free(data); + break; + case VDAGENTD_FILE_XFER_STATUS: + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_status(vdagent_file_xfers, + (VDAgentFileXferStatusMessage *)data); + } else { + vdagent_file_xfers_error(*connp, + ((VDAgentFileXferStatusMessage *)data)->id); + } + free(data); + break; + case VDAGENTD_FILE_XFER_DISABLE: + if (debug) + syslog(LOG_DEBUG, "Disabling file-xfers"); + + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_destroy(vdagent_file_xfers); + vdagent_file_xfers = NULL; + } + break; + case VDAGENTD_AUDIO_VOLUME_SYNC: { + VDAgentAudioVolumeSync *avs = (VDAgentAudioVolumeSync *)data; + if (avs->is_playback) { + vdagent_audio_playback_sync(avs->mute, avs->nchannels, avs->volume); + } else { + vdagent_audio_record_sync(avs->mute, avs->nchannels, avs->volume); + } + free(data); + break; + } + case VDAGENTD_FILE_XFER_DATA: + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_data(vdagent_file_xfers, + (VDAgentFileXferDataMessage *)data); + } else { + vdagent_file_xfers_error(*connp, + ((VDAgentFileXferDataMessage *)data)->id); + } + free(data); + break; + case VDAGENTD_CLIENT_DISCONNECTED: + vdagent_x11_client_disconnected(x11); + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_destroy(vdagent_file_xfers); + vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir, + fx_open_dir, debug); + } + break; + default: + syslog(LOG_ERR, "Unknown message from vdagentd type: %d, ignoring", + header->type); + free(data); + } +} + +static int client_setup(int reconnect) +{ + while (!quit) { + client = udscs_connect(vdagentd_socket, daemon_read_complete, NULL, + vdagentd_messages, VDAGENTD_NO_MESSAGES, + debug); + if (client || !reconnect || quit) { + break; + } + sleep(1); + } + return client == NULL; +} + +static void usage(FILE *fp) +{ + fprintf(fp, + "Usage: spice-vdagent [OPTIONS]\n\n" + "Spice guest agent X11 session agent, version %s.\n\n" + "Options:\n" + " -h print this text\n" + " -d log debug messages\n" + " -s <port> set virtio serial port\n" + " -S <filename> set udcs socket\n" + " -x don't daemonize\n" + " -f <dir|xdg-desktop|xdg-download> file xfer save dir\n" + " -o <0|1> open dir on file xfer completion\n", + VERSION); +} + +static void quit_handler(int sig) +{ + quit = 1; +} + +/* When we daemonize, it is useful to have the main process + wait to make sure the X connection worked. We wait up + to 10 seconds to get an 'all clear' from the child + before we exit. If we don't, we're able to exit with a + status that indicates an error occured */ +static void wait_and_exit(int s) +{ + char buf[4]; + struct pollfd p; + p.fd = s; + p.events = POLLIN; + + if (poll(&p, 1, 10000) > 0) + if (read(s, buf, sizeof(buf)) > 0) + exit(0); + + exit(1); +} + +static int daemonize(void) +{ + int x; + int fd[2]; + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) { + syslog(LOG_ERR, "socketpair : %s", strerror(errno)); + exit(1); + } + + /* detach from terminal */ + switch (fork()) { + case 0: + close(0); close(1); close(2); + setsid(); + x = open("/dev/null", O_RDWR); x = dup(x); x = dup(x); + close(fd[0]); + return fd[1]; + case -1: + syslog(LOG_ERR, "fork: %s", strerror(errno)); + exit(1); + default: + close(fd[1]); + wait_and_exit(fd[0]); + } + + return 0; +} + +static int file_test(const char *path) +{ + struct stat buffer; + + return stat(path, &buffer); +} + +int main(int argc, char *argv[]) +{ + fd_set readfds, writefds; + int c, n, nfds, x11_fd; + int do_daemonize = 1; + int parent_socket = 0; + int x11_sync = 0; + struct sigaction act; + + for (;;) { + if (-1 == (c = getopt(argc, argv, "-dxhys:f:o:S:"))) + break; + switch (c) { + case 'd': + debug++; + break; + case 's': + portdev = optarg; + break; + case 'x': + do_daemonize = 0; + break; + case 'y': + x11_sync = 1; + break; + case 'h': + usage(stdout); + return 0; + case 'f': + fx_dir = optarg; + break; + case 'o': + fx_open_dir = atoi(optarg); + break; + case 'S': + vdagentd_socket = optarg; + break; + default: + fputs("\n", stderr); + usage(stderr); + return 1; + } + } + + memset(&act, 0, sizeof(act)); + act.sa_flags = SA_RESTART; + act.sa_handler = quit_handler; + sigaction(SIGINT, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + + openlog("spice-vdagent", do_daemonize ? LOG_PID : (LOG_PID | LOG_PERROR), + LOG_USER); + + if (file_test(portdev) != 0) { + syslog(LOG_ERR, "Cannot access vdagent virtio channel %s", portdev); + return 1; + } + + if (do_daemonize) + parent_socket = daemonize(); + +reconnect: + if (version_mismatch) { + syslog(LOG_INFO, "Version mismatch, restarting"); + sleep(1); + execvp(argv[0], argv); + } + + if (client_setup(do_daemonize)) { + return 1; + } + + x11 = vdagent_x11_create(client, debug, x11_sync); + if (!x11) { + udscs_destroy_connection(&client); + return 1; + } + + if (!fx_dir) { + if (vdagent_x11_has_icons_on_desktop(x11)) + fx_dir = "xdg-desktop"; + else + fx_dir = "xdg-download"; + } + if (fx_open_dir == -1) + fx_open_dir = !vdagent_x11_has_icons_on_desktop(x11); + if (!strcmp(fx_dir, "xdg-desktop")) + fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP); + else if (!strcmp(fx_dir, "xdg-download")) + fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD); + if (fx_dir) { + vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir, + fx_open_dir, debug); + } else { + syslog(LOG_WARNING, + "warning could not get file xfer save dir, file transfers will be disabled"); + vdagent_file_xfers = NULL; + } + + if (parent_socket) { + if (write(parent_socket, "OK", 2) != 2) + syslog(LOG_WARNING, "Parent already gone."); + close(parent_socket); + parent_socket = 0; + } + + while (client && !quit) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + nfds = udscs_client_fill_fds(client, &readfds, &writefds); + x11_fd = vdagent_x11_get_fd(x11); + FD_SET(x11_fd, &readfds); + if (x11_fd >= nfds) + nfds = x11_fd + 1; + + n = select(nfds, &readfds, &writefds, NULL, NULL); + if (n == -1) { + if (errno == EINTR) + continue; + syslog(LOG_ERR, "Fatal error select: %s", strerror(errno)); + break; + } + + if (FD_ISSET(x11_fd, &readfds)) + vdagent_x11_do_read(x11); + udscs_client_handle_fds(&client, &readfds, &writefds); + } + + if (vdagent_file_xfers != NULL) { + vdagent_file_xfers_destroy(vdagent_file_xfers); + } + vdagent_x11_destroy(x11, client == NULL); + udscs_destroy_connection(&client); + if (!quit && do_daemonize) + goto reconnect; + + return 0; +} |