CSConnection.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. //
  2. // Copyright © 2019 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "CocoaSpice.h"
  17. #import "UTMLogging.h"
  18. #import <glib.h>
  19. #import <spice-client.h>
  20. #import <spice/vd_agent.h>
  21. @interface CSConnection ()
  22. @property (nonatomic, readwrite) CSSession *session;
  23. @property (nonatomic, readwrite) CSUSBManager *usbManager;
  24. @property (nonatomic, readwrite) CSInput *input;
  25. @property (nonatomic, readwrite) SpiceSession *spiceSession;
  26. @property (nonatomic, readwrite) SpiceMainChannel *spiceMain;
  27. @property (nonatomic, readwrite) SpiceAudio *spiceAudio;
  28. @property (nonatomic, readwrite) NSArray<CSDisplayMetal *> *monitors;
  29. @end
  30. @implementation CSConnection
  31. static void cs_main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
  32. gpointer data)
  33. {
  34. CSConnection *self = (__bridge CSConnection *)data;
  35. const GError *error = NULL;
  36. NSString *genericMsg = NSLocalizedString(@"An error occurred trying to connect to SPICE.", @"CSConnection");
  37. switch (event) {
  38. case SPICE_CHANNEL_OPENED:
  39. g_message("main channel: opened");
  40. [self.delegate spiceConnected:self];
  41. break;
  42. case SPICE_CHANNEL_SWITCHING:
  43. g_message("main channel: switching host");
  44. break;
  45. case SPICE_CHANNEL_CLOSED:
  46. /* this event is only sent if the channel was succesfully opened before */
  47. g_message("main channel: closed");
  48. spice_session_disconnect(self.spiceSession);
  49. break;
  50. case SPICE_CHANNEL_ERROR_IO:
  51. case SPICE_CHANNEL_ERROR_TLS:
  52. case SPICE_CHANNEL_ERROR_LINK:
  53. case SPICE_CHANNEL_ERROR_CONNECT:
  54. case SPICE_CHANNEL_ERROR_AUTH:
  55. error = spice_channel_get_error(channel);
  56. if (error) {
  57. g_message("channel error: %s", error->message);
  58. }
  59. [self.delegate spiceError:self err:(error ? [NSString stringWithUTF8String:error->message] : genericMsg)];
  60. break;
  61. default:
  62. /* TODO: more sophisticated error handling */
  63. g_warning("unknown main channel event: %u", event);
  64. /* connection_disconnect(conn); */
  65. break;
  66. }
  67. }
  68. static void cs_display_monitors(SpiceChannel *display, GParamSpec *pspec,
  69. gpointer data)
  70. {
  71. CSConnection *self = (__bridge CSConnection *)data;
  72. GArray *cfgs = NULL;
  73. SpiceDisplayMonitorConfig *cfg = NULL;
  74. int chid;
  75. g_object_get(display,
  76. "channel-id", &chid,
  77. "monitors", &cfgs,
  78. NULL);
  79. g_return_if_fail(cfgs != NULL);
  80. NSMutableIndexSet *markedItems = [NSMutableIndexSet indexSet];
  81. NSMutableArray<CSDisplayMetal *> *oldMonitors = [self.monitors mutableCopy];
  82. NSMutableArray<CSDisplayMetal *> *newMonitors = [NSMutableArray array];
  83. // mark monitors that are in use
  84. for (int i = 0; i < cfgs->len; i++) {
  85. cfg = &g_array_index(cfgs, SpiceDisplayMonitorConfig, i);
  86. int j;
  87. for (j = 0; j < oldMonitors.count; j++) {
  88. CSDisplayMetal *monitor = oldMonitors[j];
  89. if (cfg->id == monitor.monitorID && chid == monitor.channelID) {
  90. [markedItems addIndex:j];
  91. break;
  92. }
  93. }
  94. if (j == oldMonitors.count) { // not seen
  95. CSDisplayMetal *monitor = [[CSDisplayMetal alloc] initWithSession:self.spiceSession channelID:chid monitorID:i];
  96. [newMonitors addObject:monitor];
  97. [self.delegate spiceDisplayCreated:self display:monitor];
  98. }
  99. }
  100. // mark monitors that are in other channels
  101. for (int j = 0; j < oldMonitors.count; j++) {
  102. CSDisplayMetal *monitor = oldMonitors[j];
  103. if (chid != monitor.channelID) {
  104. [markedItems addIndex:j];
  105. }
  106. }
  107. // set the new monitors array
  108. NSMutableArray<CSDisplayMetal *> *monitors = [[oldMonitors objectsAtIndexes:markedItems] mutableCopy];
  109. [oldMonitors removeObjectsAtIndexes:markedItems];
  110. [monitors addObjectsFromArray:newMonitors];
  111. self.monitors = monitors;
  112. // remove old monitors
  113. for (CSDisplayMetal *monitor in oldMonitors) {
  114. [self.delegate spiceDisplayDestroyed:self display:monitor];
  115. }
  116. g_clear_pointer(&cfgs, g_array_unref);
  117. }
  118. static void cs_main_agent_update(SpiceChannel *main, gpointer data)
  119. {
  120. CSConnection *self = (__bridge CSConnection *)data;
  121. gboolean agent_connected = false;
  122. CSConnectionAgentFeature features = kCSConnectionAgentFeatureNone;
  123. g_object_get(main, "agent-connected", &agent_connected, NULL);
  124. UTMLog(@"SPICE agent connected: %d", agent_connected);
  125. if (agent_connected) {
  126. if (spice_main_channel_agent_test_capability(SPICE_MAIN_CHANNEL(main), VD_AGENT_CAP_MONITORS_CONFIG)) {
  127. features |= kCSConnectionAgentFeatureMonitorsConfig;
  128. }
  129. [self.delegate spiceAgentConnected:self supportingFeatures:features];
  130. } else {
  131. [self.delegate spiceAgentDisconnected:self];
  132. }
  133. }
  134. static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
  135. {
  136. CSConnection *self = (__bridge CSConnection *)data;
  137. int chid;
  138. g_object_get(channel, "channel-id", &chid, NULL);
  139. SPICE_DEBUG("new channel (#%d)", chid);
  140. if (SPICE_IS_MAIN_CHANNEL(channel)) {
  141. SPICE_DEBUG("new main channel");
  142. g_assert(!self.spiceMain); // should only be 1 main channel
  143. self.spiceMain = SPICE_MAIN_CHANNEL(channel);
  144. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  145. g_signal_connect(channel, "channel-event",
  146. G_CALLBACK(cs_main_channel_event), GLIB_OBJC_RETAIN(self));
  147. g_signal_connect(channel, "main_agent_update",
  148. G_CALLBACK(cs_main_agent_update), GLIB_OBJC_RETAIN(self));
  149. }
  150. if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
  151. SPICE_DEBUG("new display channel (#%d)", chid);
  152. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  153. g_signal_connect(channel, "notify::monitors",
  154. G_CALLBACK(cs_display_monitors), GLIB_OBJC_RETAIN(self));
  155. spice_channel_connect(channel);
  156. }
  157. if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
  158. SPICE_DEBUG("new audio channel");
  159. if (self.audioEnabled) {
  160. self.spiceAudio = spice_audio_get(s, [CSMain sharedInstance].glibMainContext);
  161. spice_channel_connect(channel);
  162. } else {
  163. SPICE_DEBUG("audio disabled");
  164. }
  165. }
  166. if (SPICE_IS_PORT_CHANNEL(channel)) {
  167. SPICE_DEBUG("new port channel");
  168. spice_channel_connect(channel);
  169. }
  170. }
  171. static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
  172. {
  173. CSConnection *self = (__bridge CSConnection *)data;
  174. int chid;
  175. g_object_get(channel, "channel-id", &chid, NULL);
  176. if (SPICE_IS_MAIN_CHANNEL(channel)) {
  177. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  178. SPICE_DEBUG("zap main channel");
  179. self.spiceMain = NULL;
  180. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_main_channel_event), GLIB_OBJC_RELEASE(self));
  181. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_main_agent_update), GLIB_OBJC_RELEASE(self));
  182. }
  183. if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
  184. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  185. SPICE_DEBUG("zap display channel (#%d)", chid);
  186. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_display_monitors), GLIB_OBJC_RELEASE(self));
  187. for (CSDisplayMetal *monitor in self.monitors) {
  188. [self.delegate spiceDisplayDestroyed:self display:monitor];
  189. }
  190. self.monitors = [NSArray<CSDisplayMetal *> array];
  191. }
  192. if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
  193. SPICE_DEBUG("zap audio channel");
  194. self.spiceAudio = NULL;
  195. }
  196. }
  197. static void cs_connection_destroy(SpiceSession *session,
  198. gpointer data)
  199. {
  200. CSConnection *self = (__bridge CSConnection *)data;
  201. [self.delegate spiceDisconnected:self];
  202. }
  203. - (void)setHost:(NSString *)host {
  204. g_object_set(self.spiceSession, "host", [host UTF8String], NULL);
  205. }
  206. - (NSString *)host {
  207. gchar *strhost;
  208. g_object_get(self.spiceSession, "host", &strhost, NULL);
  209. NSString *nshost = [NSString stringWithUTF8String:strhost];
  210. g_free(strhost);
  211. return nshost;
  212. }
  213. - (void)setPort:(NSString *)port {
  214. g_object_set(self.spiceSession, "port", [port UTF8String], NULL);
  215. }
  216. - (NSString *)port {
  217. gchar *strhost;
  218. g_object_get(self.spiceSession, "port", &strhost, NULL);
  219. NSString *nshost = [NSString stringWithUTF8String:strhost];
  220. g_free(strhost);
  221. return nshost;
  222. }
  223. - (void)setUnixSocketURL:(NSURL *)unixSocketURL {
  224. g_object_set(self.spiceSession, "unix-path", unixSocketURL.path.UTF8String, NULL);
  225. _unixSocketURL = unixSocketURL;
  226. }
  227. - (void)dealloc {
  228. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  229. g_signal_handlers_disconnect_by_func(self.spiceSession, G_CALLBACK(cs_channel_new), GLIB_OBJC_RELEASE(self));
  230. g_signal_handlers_disconnect_by_func(self.spiceSession, G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RELEASE(self));
  231. g_signal_handlers_disconnect_by_func(self.spiceSession, G_CALLBACK(cs_connection_destroy), GLIB_OBJC_RELEASE(self));
  232. g_object_unref(self.spiceSession);
  233. self.spiceSession = NULL;
  234. }
  235. - (void)finishInit {
  236. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  237. g_signal_connect(self.spiceSession, "channel-new",
  238. G_CALLBACK(cs_channel_new), GLIB_OBJC_RETAIN(self));
  239. g_signal_connect(self.spiceSession, "channel-destroy",
  240. G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RETAIN(self));
  241. g_signal_connect(self.spiceSession, "disconnected",
  242. G_CALLBACK(cs_connection_destroy), GLIB_OBJC_RETAIN(self));
  243. #if !defined(WITH_QEMU_TCI)
  244. SpiceUsbDeviceManager *manager = spice_usb_device_manager_get(self.spiceSession, NULL);
  245. g_assert(manager != NULL);
  246. self.usbManager = [[CSUSBManager alloc] initWithUsbDeviceManager:manager];
  247. #endif
  248. self.input = [[CSInput alloc] initWithSession:self.spiceSession];
  249. self.session = [[CSSession alloc] initWithSession:self.spiceSession];
  250. self.monitors = [NSArray<CSDisplayMetal *> array];
  251. }
  252. - (instancetype)initWithHost:(NSString *)host port:(NSString *)port {
  253. if (self = [super init]) {
  254. self.spiceSession = spice_session_new();
  255. self.host = host;
  256. self.port = port;
  257. [self finishInit];
  258. }
  259. return self;
  260. }
  261. - (instancetype)initWithUnixSocketFile:(NSURL *)socketFile {
  262. if (self = [super init]) {
  263. self.spiceSession = spice_session_new();
  264. self.unixSocketURL = socketFile;
  265. [self finishInit];
  266. }
  267. return self;
  268. }
  269. - (BOOL)connect {
  270. return spice_session_connect(self.spiceSession);
  271. }
  272. - (void)disconnect {
  273. spice_session_disconnect(self.spiceSession);
  274. }
  275. @end