spice-app.c 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. #include "qemu/osdep.h"
  25. #include <gio/gio.h>
  26. #include "ui/console.h"
  27. #include "ui/spice-display.h"
  28. #include "qemu/config-file.h"
  29. #include "qemu/error-report.h"
  30. #include "qemu/option.h"
  31. #include "qemu/cutils.h"
  32. #include "qemu/module.h"
  33. #include "qapi/error.h"
  34. #include "io/channel-command.h"
  35. #include "chardev/spice.h"
  36. #include "sysemu/sysemu.h"
  37. #include "qom/object.h"
  38. static const char *tmp_dir;
  39. static char *app_dir;
  40. static char *sock_path;
  41. struct VCChardev {
  42. SpiceChardev parent;
  43. };
  44. struct VCChardevClass {
  45. ChardevClass parent;
  46. void (*parent_open)(Chardev *chr, ChardevBackend *backend,
  47. bool *be_opened, Error **errp);
  48. };
  49. #define TYPE_CHARDEV_VC "chardev-vc"
  50. OBJECT_DECLARE_TYPE(VCChardev, VCChardevClass, CHARDEV_VC)
  51. static ChardevBackend *
  52. chr_spice_backend_new(void)
  53. {
  54. ChardevBackend *be = g_new0(ChardevBackend, 1);
  55. be->type = CHARDEV_BACKEND_KIND_SPICEPORT;
  56. be->u.spiceport.data = g_new0(ChardevSpicePort, 1);
  57. return be;
  58. }
  59. static void vc_chr_open(Chardev *chr,
  60. ChardevBackend *backend,
  61. bool *be_opened,
  62. Error **errp)
  63. {
  64. VCChardevClass *vc = CHARDEV_VC_GET_CLASS(chr);
  65. ChardevBackend *be;
  66. const char *fqdn = NULL;
  67. if (strstart(chr->label, "serial", NULL)) {
  68. fqdn = "org.qemu.console.serial.0";
  69. } else if (strstart(chr->label, "parallel", NULL)) {
  70. fqdn = "org.qemu.console.parallel.0";
  71. } else if (strstart(chr->label, "compat_monitor", NULL)) {
  72. fqdn = "org.qemu.monitor.hmp.0";
  73. }
  74. be = chr_spice_backend_new();
  75. be->u.spiceport.data->fqdn = fqdn ?
  76. g_strdup(fqdn) : g_strdup_printf("org.qemu.console.%s", chr->label);
  77. vc->parent_open(chr, be, be_opened, errp);
  78. qapi_free_ChardevBackend(be);
  79. }
  80. static void vc_chr_set_echo(Chardev *chr, bool echo)
  81. {
  82. /* TODO: set echo for frontends QMP and qtest */
  83. }
  84. static void char_vc_class_init(ObjectClass *oc, void *data)
  85. {
  86. VCChardevClass *vc = CHARDEV_VC_CLASS(oc);
  87. ChardevClass *cc = CHARDEV_CLASS(oc);
  88. vc->parent_open = cc->open;
  89. cc->parse = qemu_chr_parse_vc;
  90. cc->open = vc_chr_open;
  91. cc->chr_set_echo = vc_chr_set_echo;
  92. }
  93. static const TypeInfo char_vc_type_info = {
  94. .name = TYPE_CHARDEV_VC,
  95. .parent = TYPE_CHARDEV_SPICEPORT,
  96. .instance_size = sizeof(VCChardev),
  97. .class_init = char_vc_class_init,
  98. .class_size = sizeof(VCChardevClass),
  99. };
  100. static void spice_app_atexit(void)
  101. {
  102. if (sock_path) {
  103. unlink(sock_path);
  104. }
  105. if (tmp_dir) {
  106. rmdir(tmp_dir);
  107. }
  108. g_free(sock_path);
  109. g_free(app_dir);
  110. }
  111. static void spice_app_display_early_init(DisplayOptions *opts)
  112. {
  113. QemuOpts *qopts;
  114. QemuOptsList *list;
  115. GError *err = NULL;
  116. if (opts->has_full_screen) {
  117. error_report("spice-app full-screen isn't supported yet.");
  118. exit(1);
  119. }
  120. if (opts->has_window_close) {
  121. error_report("spice-app window-close isn't supported yet.");
  122. exit(1);
  123. }
  124. atexit(spice_app_atexit);
  125. if (qemu_name) {
  126. app_dir = g_build_filename(g_get_user_runtime_dir(),
  127. "qemu", qemu_name, NULL);
  128. if (g_mkdir_with_parents(app_dir, S_IRWXU) < -1) {
  129. error_report("Failed to create directory %s: %s",
  130. app_dir, strerror(errno));
  131. exit(1);
  132. }
  133. } else {
  134. app_dir = g_dir_make_tmp(NULL, &err);
  135. tmp_dir = app_dir;
  136. if (err) {
  137. error_report("Failed to create temporary directory: %s",
  138. err->message);
  139. exit(1);
  140. }
  141. }
  142. list = qemu_find_opts("spice");
  143. if (list == NULL) {
  144. error_report("spice-app missing spice support");
  145. exit(1);
  146. }
  147. type_register(&char_vc_type_info);
  148. sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL);
  149. qopts = qemu_opts_create(list, NULL, 0, &error_abort);
  150. qemu_opt_set(qopts, "disable-ticketing", "on", &error_abort);
  151. qemu_opt_set(qopts, "unix", "on", &error_abort);
  152. qemu_opt_set(qopts, "addr", sock_path, &error_abort);
  153. qemu_opt_set(qopts, "image-compression", "off", &error_abort);
  154. qemu_opt_set(qopts, "streaming-video", "off", &error_abort);
  155. #ifdef HAVE_SPICE_GL
  156. qemu_opt_set(qopts, "gl", opts->has_gl ? "on" : "off", &error_abort);
  157. display_opengl = opts->has_gl;
  158. #endif
  159. }
  160. static void spice_app_display_init(DisplayState *ds, DisplayOptions *opts)
  161. {
  162. ChardevBackend *be = chr_spice_backend_new();
  163. QemuOpts *qopts;
  164. GError *err = NULL;
  165. gchar *uri;
  166. be->u.spiceport.data->fqdn = g_strdup("org.qemu.monitor.qmp.0");
  167. qemu_chardev_new("org.qemu.monitor.qmp", TYPE_CHARDEV_SPICEPORT,
  168. be, NULL, &error_abort);
  169. qopts = qemu_opts_create(qemu_find_opts("mon"),
  170. NULL, 0, &error_fatal);
  171. qemu_opt_set(qopts, "chardev", "org.qemu.monitor.qmp", &error_abort);
  172. qemu_opt_set(qopts, "mode", "control", &error_abort);
  173. qapi_free_ChardevBackend(be);
  174. uri = g_strjoin("", "spice+unix://", app_dir, "/", "spice.sock", NULL);
  175. info_report("Launching display with URI: %s", uri);
  176. g_app_info_launch_default_for_uri(uri, NULL, &err);
  177. if (err) {
  178. error_report("Failed to launch %s URI: %s", uri, err->message);
  179. error_report("You need a capable Spice client, "
  180. "such as virt-viewer 8.0");
  181. exit(1);
  182. }
  183. g_free(uri);
  184. }
  185. static QemuDisplay qemu_display_spice_app = {
  186. .type = DISPLAY_TYPE_SPICE_APP,
  187. .early_init = spice_app_display_early_init,
  188. .init = spice_app_display_init,
  189. };
  190. static void register_spice_app(void)
  191. {
  192. qemu_display_register(&qemu_display_spice_app);
  193. }
  194. type_init(register_spice_app);
  195. module_dep("ui-spice-core");
  196. module_dep("chardev-spice");