xref: /qemu/ui/spice-app.c (revision d0fb9657a33dd3d1db1b492c4dcc7c778e40e5c0)
1  /*
2   * QEMU external Spice client display driver
3   *
4   * Copyright (c) 2018 Marc-AndrĂ© Lureau <marcandre.lureau@redhat.com>
5   *
6   * Permission is hereby granted, free of charge, to any person obtaining a copy
7   * of this software and associated documentation files (the "Software"), to deal
8   * in the Software without restriction, including without limitation the rights
9   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  
25  #include "qemu/osdep.h"
26  
27  #include <gio/gio.h>
28  
29  #include "ui/console.h"
30  #include "qemu/config-file.h"
31  #include "qemu/option.h"
32  #include "qemu/cutils.h"
33  #include "qemu/module.h"
34  #include "qapi/error.h"
35  #include "io/channel-command.h"
36  #include "chardev/spice.h"
37  #include "sysemu/sysemu.h"
38  #include "qom/object.h"
39  
40  static const char *tmp_dir;
41  static char *app_dir;
42  static char *sock_path;
43  
44  struct VCChardev {
45      SpiceChardev parent;
46  };
47  
48  struct VCChardevClass {
49      ChardevClass parent;
50      void (*parent_open)(Chardev *chr, ChardevBackend *backend,
51                          bool *be_opened, Error **errp);
52  };
53  
54  #define TYPE_CHARDEV_VC "chardev-vc"
55  OBJECT_DECLARE_TYPE(VCChardev, VCChardevClass, CHARDEV_VC)
56  
57  static ChardevBackend *
58  chr_spice_backend_new(void)
59  {
60      ChardevBackend *be = g_new0(ChardevBackend, 1);
61  
62      be->type = CHARDEV_BACKEND_KIND_SPICEPORT;
63      be->u.spiceport.data = g_new0(ChardevSpicePort, 1);
64  
65      return be;
66  }
67  
68  static void vc_chr_open(Chardev *chr,
69                          ChardevBackend *backend,
70                          bool *be_opened,
71                          Error **errp)
72  {
73      VCChardevClass *vc = CHARDEV_VC_GET_CLASS(chr);
74      ChardevBackend *be;
75      const char *fqdn = NULL;
76  
77      if (strstart(chr->label, "serial", NULL)) {
78          fqdn = "org.qemu.console.serial.0";
79      } else if (strstart(chr->label, "parallel", NULL)) {
80          fqdn = "org.qemu.console.parallel.0";
81      } else if (strstart(chr->label, "compat_monitor", NULL)) {
82          fqdn = "org.qemu.monitor.hmp.0";
83      }
84  
85      be = chr_spice_backend_new();
86      be->u.spiceport.data->fqdn = fqdn ?
87          g_strdup(fqdn) : g_strdup_printf("org.qemu.console.%s", chr->label);
88      vc->parent_open(chr, be, be_opened, errp);
89      qapi_free_ChardevBackend(be);
90  }
91  
92  static void vc_chr_set_echo(Chardev *chr, bool echo)
93  {
94      /* TODO: set echo for frontends QMP and qtest */
95  }
96  
97  static void char_vc_class_init(ObjectClass *oc, void *data)
98  {
99      VCChardevClass *vc = CHARDEV_VC_CLASS(oc);
100      ChardevClass *cc = CHARDEV_CLASS(oc);
101  
102      vc->parent_open = cc->open;
103  
104      cc->parse = qemu_chr_parse_vc;
105      cc->open = vc_chr_open;
106      cc->chr_set_echo = vc_chr_set_echo;
107  }
108  
109  static const TypeInfo char_vc_type_info = {
110      .name = TYPE_CHARDEV_VC,
111      .parent = TYPE_CHARDEV_SPICEPORT,
112      .instance_size = sizeof(VCChardev),
113      .class_init = char_vc_class_init,
114      .class_size = sizeof(VCChardevClass),
115  };
116  
117  static void spice_app_atexit(void)
118  {
119      if (sock_path) {
120          unlink(sock_path);
121      }
122      if (tmp_dir) {
123          rmdir(tmp_dir);
124      }
125      g_free(sock_path);
126      g_free(app_dir);
127  }
128  
129  static void spice_app_display_early_init(DisplayOptions *opts)
130  {
131      QemuOpts *qopts;
132      QemuOptsList *list;
133      GError *err = NULL;
134  
135      if (opts->has_full_screen) {
136          error_report("spice-app full-screen isn't supported yet.");
137          exit(1);
138      }
139      if (opts->has_window_close) {
140          error_report("spice-app window-close isn't supported yet.");
141          exit(1);
142      }
143  
144      atexit(spice_app_atexit);
145  
146      if (qemu_name) {
147          app_dir = g_build_filename(g_get_user_runtime_dir(),
148                                     "qemu", qemu_name, NULL);
149          if (g_mkdir_with_parents(app_dir, S_IRWXU) < -1) {
150              error_report("Failed to create directory %s: %s",
151                           app_dir, strerror(errno));
152              exit(1);
153          }
154      } else {
155          app_dir = g_dir_make_tmp(NULL, &err);
156          tmp_dir = app_dir;
157          if (err) {
158              error_report("Failed to create temporary directory: %s",
159                           err->message);
160              exit(1);
161          }
162      }
163      list = qemu_find_opts("spice");
164      if (list == NULL) {
165          error_report("spice-app missing spice support");
166          exit(1);
167      }
168  
169      type_register(&char_vc_type_info);
170  
171      sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL);
172      qopts = qemu_opts_create(list, NULL, 0, &error_abort);
173      qemu_opt_set(qopts, "disable-ticketing", "on", &error_abort);
174      qemu_opt_set(qopts, "unix", "on", &error_abort);
175      qemu_opt_set(qopts, "addr", sock_path, &error_abort);
176      qemu_opt_set(qopts, "image-compression", "off", &error_abort);
177      qemu_opt_set(qopts, "streaming-video", "off", &error_abort);
178  #ifdef CONFIG_OPENGL
179      qemu_opt_set(qopts, "gl", opts->has_gl ? "on" : "off", &error_abort);
180      display_opengl = opts->has_gl;
181  #endif
182  }
183  
184  static void spice_app_display_init(DisplayState *ds, DisplayOptions *opts)
185  {
186      ChardevBackend *be = chr_spice_backend_new();
187      QemuOpts *qopts;
188      GError *err = NULL;
189      gchar *uri;
190  
191      be->u.spiceport.data->fqdn = g_strdup("org.qemu.monitor.qmp.0");
192      qemu_chardev_new("org.qemu.monitor.qmp", TYPE_CHARDEV_SPICEPORT,
193                       be, NULL, &error_abort);
194      qopts = qemu_opts_create(qemu_find_opts("mon"),
195                               NULL, 0, &error_fatal);
196      qemu_opt_set(qopts, "chardev", "org.qemu.monitor.qmp", &error_abort);
197      qemu_opt_set(qopts, "mode", "control", &error_abort);
198  
199      qapi_free_ChardevBackend(be);
200      uri = g_strjoin("", "spice+unix://", app_dir, "/", "spice.sock", NULL);
201      info_report("Launching display with URI: %s", uri);
202      g_app_info_launch_default_for_uri(uri, NULL, &err);
203      if (err) {
204          error_report("Failed to launch %s URI: %s", uri, err->message);
205          error_report("You need a capable Spice client, "
206                       "such as virt-viewer 8.0");
207          exit(1);
208      }
209      g_free(uri);
210  }
211  
212  static QemuDisplay qemu_display_spice_app = {
213      .type       = DISPLAY_TYPE_SPICE_APP,
214      .early_init = spice_app_display_early_init,
215      .init       = spice_app_display_init,
216  };
217  
218  static void register_spice_app(void)
219  {
220      qemu_display_register(&qemu_display_spice_app);
221  }
222  
223  type_init(register_spice_app);
224