vdagent: volume synchronization from client.
[vd_agent.git] / src / vdagent.c
blob348dfbd27c79837f73c54f0e1250b29717313778
1 /* vdagent.c xorg-client to vdagentd (daemon).
3 Copyright 2010-2013 Red Hat, Inc.
5 Red Hat Authors:
6 Hans de Goede <hdegoede@redhat.com>
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <sys/select.h>
35 #include <sys/stat.h>
36 #include <spice/vd_agent.h>
37 #include <glib.h>
38 #include <poll.h>
40 #include "udscs.h"
41 #include "vdagentd-proto.h"
42 #include "vdagentd-proto-strings.h"
43 #include "vdagent-audio.h"
44 #include "vdagent-x11.h"
45 #include "vdagent-file-xfers.h"
47 static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
48 static const char *vdagentd_socket = VDAGENTD_SOCKET;
49 static int debug = 0;
50 static const char *fx_dir = NULL;
51 static int fx_open_dir = -1;
52 static struct vdagent_x11 *x11 = NULL;
53 static struct vdagent_file_xfers *vdagent_file_xfers = NULL;
54 static struct udscs_connection *client = NULL;
55 static int quit = 0;
56 static int version_mismatch = 0;
58 void daemon_read_complete(struct udscs_connection **connp,
59 struct udscs_message_header *header, uint8_t *data)
61 switch (header->type) {
62 case VDAGENTD_MONITORS_CONFIG:
63 vdagent_x11_set_monitor_config(x11, (VDAgentMonitorsConfig *)data, 0);
64 free(data);
65 break;
66 case VDAGENTD_CLIPBOARD_REQUEST:
67 vdagent_x11_clipboard_request(x11, header->arg1, header->arg2);
68 free(data);
69 break;
70 case VDAGENTD_CLIPBOARD_GRAB:
71 vdagent_x11_clipboard_grab(x11, header->arg1, (uint32_t *)data,
72 header->size / sizeof(uint32_t));
73 free(data);
74 break;
75 case VDAGENTD_CLIPBOARD_DATA:
76 vdagent_x11_clipboard_data(x11, header->arg1, header->arg2,
77 data, header->size);
78 /* vdagent_x11_clipboard_data takes ownership of the data (or frees
79 it immediately) */
80 break;
81 case VDAGENTD_CLIPBOARD_RELEASE:
82 vdagent_x11_clipboard_release(x11, header->arg1);
83 free(data);
84 break;
85 case VDAGENTD_VERSION:
86 if (strcmp((char *)data, VERSION) != 0) {
87 syslog(LOG_INFO, "vdagentd version mismatch: got %s expected %s",
88 data, VERSION);
89 udscs_destroy_connection(connp);
90 version_mismatch = 1;
92 break;
93 case VDAGENTD_FILE_XFER_START:
94 if (vdagent_file_xfers != NULL) {
95 vdagent_file_xfers_start(vdagent_file_xfers,
96 (VDAgentFileXferStartMessage *)data);
97 } else {
98 vdagent_file_xfers_error(*connp,
99 ((VDAgentFileXferStartMessage *)data)->id);
101 free(data);
102 break;
103 case VDAGENTD_FILE_XFER_STATUS:
104 if (vdagent_file_xfers != NULL) {
105 vdagent_file_xfers_status(vdagent_file_xfers,
106 (VDAgentFileXferStatusMessage *)data);
107 } else {
108 vdagent_file_xfers_error(*connp,
109 ((VDAgentFileXferStatusMessage *)data)->id);
111 free(data);
112 break;
113 case VDAGENTD_AUDIO_VOLUME_SYNC: {
114 VDAgentAudioVolumeSync *avs = (VDAgentAudioVolumeSync *)data;
115 if (avs->is_playback) {
116 vdagent_audio_playback_sync(avs->mute, avs->nchannels, avs->volume);
117 } else {
118 vdagent_audio_record_sync(avs->mute, avs->nchannels, avs->volume);
120 free(data);
121 break;
123 case VDAGENTD_FILE_XFER_DATA:
124 if (vdagent_file_xfers != NULL) {
125 vdagent_file_xfers_data(vdagent_file_xfers,
126 (VDAgentFileXferDataMessage *)data);
127 } else {
128 vdagent_file_xfers_error(*connp,
129 ((VDAgentFileXferDataMessage *)data)->id);
131 free(data);
132 break;
133 case VDAGENTD_CLIENT_DISCONNECTED:
134 vdagent_x11_client_disconnected(x11);
135 if (vdagent_file_xfers != NULL) {
136 vdagent_file_xfers_destroy(vdagent_file_xfers);
137 vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir,
138 fx_open_dir, debug);
140 break;
141 default:
142 syslog(LOG_ERR, "Unknown message from vdagentd type: %d, ignoring",
143 header->type);
144 free(data);
148 int client_setup(int reconnect)
150 while (!quit) {
151 client = udscs_connect(vdagentd_socket, daemon_read_complete, NULL,
152 vdagentd_messages, VDAGENTD_NO_MESSAGES,
153 debug);
154 if (client || !reconnect || quit) {
155 break;
157 sleep(1);
159 return client == NULL;
162 static void usage(FILE *fp)
164 fprintf(fp,
165 "Usage: spice-vdagent [OPTIONS]\n\n"
166 "Spice guest agent X11 session agent, version %s.\n\n"
167 "Options:\n"
168 " -h print this text\n"
169 " -d log debug messages\n"
170 " -s <port> set virtio serial port\n"
171 " -S <filename> set udcs socket\n"
172 " -x don't daemonize\n"
173 " -f <dir|xdg-desktop|xdg-download> file xfer save dir\n"
174 " -o <0|1> open dir on file xfer completion\n",
175 VERSION);
178 static void quit_handler(int sig)
180 quit = 1;
183 /* When we daemonize, it is useful to have the main process
184 wait to make sure the X connection worked. We wait up
185 to 10 seconds to get an 'all clear' from the child
186 before we exit. If we don't, we're able to exit with a
187 status that indicates an error occured */
188 void wait_and_exit(int s)
190 char buf[4];
191 struct pollfd p;
192 p.fd = s;
193 p.events = POLLIN;
195 if (poll(&p, 1, 10000) > 0)
196 if (read(s, buf, sizeof(buf)) > 0)
197 exit(0);
199 exit(1);
202 int daemonize(void)
204 int x;
205 int fd[2];
207 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
208 syslog(LOG_ERR, "socketpair : %s", strerror(errno));
209 exit(1);
212 /* detach from terminal */
213 switch (fork()) {
214 case 0:
215 close(0); close(1); close(2);
216 setsid();
217 x = open("/dev/null", O_RDWR); x = dup(x); x = dup(x);
218 close(fd[0]);
219 return fd[1];
220 case -1:
221 syslog(LOG_ERR, "fork: %s", strerror(errno));
222 exit(1);
223 default:
224 close(fd[1]);
225 wait_and_exit(fd[0]);
228 return 0;
231 static int file_test(const char *path)
233 struct stat buffer;
235 return stat(path, &buffer);
238 int main(int argc, char *argv[])
240 fd_set readfds, writefds;
241 int c, n, nfds, x11_fd;
242 int do_daemonize = 1;
243 int parent_socket = 0;
244 int x11_sync = 0;
245 struct sigaction act;
247 for (;;) {
248 if (-1 == (c = getopt(argc, argv, "-dxhys:f:o:S:")))
249 break;
250 switch (c) {
251 case 'd':
252 debug++;
253 break;
254 case 's':
255 portdev = optarg;
256 break;
257 case 'x':
258 do_daemonize = 0;
259 break;
260 case 'y':
261 x11_sync = 1;
262 break;
263 case 'h':
264 usage(stdout);
265 return 0;
266 case 'f':
267 fx_dir = optarg;
268 break;
269 case 'o':
270 fx_open_dir = atoi(optarg);
271 break;
272 case 'S':
273 vdagentd_socket = optarg;
274 break;
275 default:
276 fputs("\n", stderr);
277 usage(stderr);
278 return 1;
282 memset(&act, 0, sizeof(act));
283 act.sa_flags = SA_RESTART;
284 act.sa_handler = quit_handler;
285 sigaction(SIGINT, &act, NULL);
286 sigaction(SIGHUP, &act, NULL);
287 sigaction(SIGTERM, &act, NULL);
288 sigaction(SIGQUIT, &act, NULL);
290 openlog("spice-vdagent", do_daemonize ? LOG_PID : (LOG_PID | LOG_PERROR),
291 LOG_USER);
293 if (file_test(portdev) != 0) {
294 return 0;
297 if (do_daemonize)
298 parent_socket = daemonize();
300 reconnect:
301 if (version_mismatch) {
302 syslog(LOG_INFO, "Version mismatch, restarting");
303 sleep(1);
304 execvp(argv[0], argv);
307 if (client_setup(do_daemonize)) {
308 return 1;
311 x11 = vdagent_x11_create(client, debug, x11_sync);
312 if (!x11) {
313 udscs_destroy_connection(&client);
314 return 1;
317 if (!fx_dir) {
318 if (vdagent_x11_has_icons_on_desktop(x11))
319 fx_dir = "xdg-desktop";
320 else
321 fx_dir = "xdg-download";
323 if (fx_open_dir == -1)
324 fx_open_dir = !vdagent_x11_has_icons_on_desktop(x11);
325 if (!strcmp(fx_dir, "xdg-desktop"))
326 fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
327 else if (!strcmp(fx_dir, "xdg-download"))
328 fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);
329 if (fx_dir) {
330 vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir,
331 fx_open_dir, debug);
332 } else {
333 syslog(LOG_WARNING,
334 "warning could not get file xfer save dir, file transfers will be disabled");
335 vdagent_file_xfers = NULL;
338 if (parent_socket) {
339 if (write(parent_socket, "OK", 2) != 2)
340 syslog(LOG_WARNING, "Parent already gone.");
341 close(parent_socket);
342 parent_socket = 0;
345 while (client && !quit) {
346 FD_ZERO(&readfds);
347 FD_ZERO(&writefds);
349 nfds = udscs_client_fill_fds(client, &readfds, &writefds);
350 x11_fd = vdagent_x11_get_fd(x11);
351 FD_SET(x11_fd, &readfds);
352 if (x11_fd >= nfds)
353 nfds = x11_fd + 1;
355 n = select(nfds, &readfds, &writefds, NULL, NULL);
356 if (n == -1) {
357 if (errno == EINTR)
358 continue;
359 syslog(LOG_ERR, "Fatal error select: %s", strerror(errno));
360 break;
363 if (FD_ISSET(x11_fd, &readfds))
364 vdagent_x11_do_read(x11);
365 udscs_client_handle_fds(&client, &readfds, &writefds);
368 if (vdagent_file_xfers != NULL) {
369 vdagent_file_xfers_destroy(vdagent_file_xfers);
371 vdagent_x11_destroy(x11, client == NULL);
372 udscs_destroy_connection(&client);
373 if (!quit && do_daemonize)
374 goto reconnect;
376 return 0;