summaryrefslogtreecommitdiff
path: root/src/vdagent
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2014-05-26 17:25:43 +0200
committerVictor Toso <me@victortoso.com>2016-09-30 14:27:11 +0200
commit22e32f29a66aec95651a8727ac730fdcb7cb1919 (patch)
tree9464063d702b72c5096d388d2df50919d08a3846 /src/vdagent
parent658fbb8ea6d81bd6188f1ad421341518c3662f8c (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.c168
-rw-r--r--src/vdagent/vdagent-audio.h27
-rw-r--r--src/vdagent/vdagent-file-xfers.c325
-rw-r--r--src/vdagent/vdagent-file-xfers.h43
-rw-r--r--src/vdagent/vdagent-x11-priv.h155
-rw-r--r--src/vdagent/vdagent-x11-randr.c981
-rw-r--r--src/vdagent/vdagent-x11.c1356
-rw-r--r--src/vdagent/vdagent-x11.h53
-rw-r--r--src/vdagent/vdagent.c387
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*)&timestamp, 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;
+}