syphon.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. #import <AppKit/AppKit.h>
  2. #import <IOSurface/IOSurface.h>
  3. #import <ScriptingBridge/ScriptingBridge.h>
  4. #import <Syphon/Syphon.h>
  5. #import <obs-module.h>
  6. #import <AvailabilityMacros.h>
  7. #import "SyphonOBSClient.h"
  8. #define LOG(level, message, ...) blog(level, "%s: " message, obs_source_get_name(s->source), ##__VA_ARGS__)
  9. struct syphon {
  10. SyphonOBSClient *client;
  11. IOSurfaceRef ref;
  12. gs_samplerstate_t *sampler;
  13. gs_effect_t *effect;
  14. gs_vertbuffer_t *vertbuffer;
  15. gs_texture_t *tex;
  16. uint32_t width, height;
  17. bool crop;
  18. CGRect crop_rect;
  19. bool allow_transparency;
  20. obs_source_t *source;
  21. bool active;
  22. bool uuid_changed;
  23. id new_server_listener;
  24. id retire_listener;
  25. NSString *app_name;
  26. NSString *name;
  27. NSString *uuid;
  28. };
  29. typedef struct syphon *syphon_t;
  30. static inline void update_properties(syphon_t s)
  31. {
  32. obs_source_update_properties(s->source);
  33. }
  34. static const char *syphon_get_name(void *unused __attribute((unused)))
  35. {
  36. return obs_module_text("Syphon");
  37. }
  38. static void stop_client(syphon_t s)
  39. {
  40. obs_enter_graphics();
  41. if (s->client) {
  42. [s->client stop];
  43. }
  44. if (s->tex) {
  45. gs_texture_destroy(s->tex);
  46. s->tex = NULL;
  47. }
  48. if (s->ref) {
  49. IOSurfaceDecrementUseCount(s->ref);
  50. CFRelease(s->ref);
  51. s->ref = NULL;
  52. }
  53. s->width = 0;
  54. s->height = 0;
  55. obs_leave_graphics();
  56. }
  57. static inline NSDictionary *find_by_uuid(NSArray *arr, NSString *uuid)
  58. {
  59. for (NSDictionary *dict in arr) {
  60. if ([dict[SyphonServerDescriptionUUIDKey] isEqual:uuid])
  61. return dict;
  62. }
  63. return nil;
  64. }
  65. /* If you see this and think "surely these must be defined in some type of
  66. * public header!": They don't. Or at least I couldnt't find anything. The
  67. * definitions inside of Syphon are only in SyphonPrivate.h/m, and nowhere
  68. * else. When we had Syphon as a submodule we abused this by using extern, but
  69. * now that we use a prebuilt framework and as such no longer can acceess the
  70. * private headers and sources directly, that's no longer possible. Other
  71. * projects sometimes copy SyphonPrivate.h entirely (with the full definitions
  72. * of everything), but for our purpose these strings are enough. */
  73. const NSString *SyphonServerDescriptionDictionaryVersionKey = @"SyphonServerDescriptionDictionaryVersionKey";
  74. const NSString *SyphonSurfaceType = @"SyphonSurfaceType";
  75. const NSString *SyphonSurfaceTypeIOSurface = @"SyphonSurfaceTypeIOSurface";
  76. const NSString *SyphonServerDescriptionSurfacesKey = @"SyphonServerDescriptionSurfacesKey";
  77. static inline void check_version(syphon_t s, NSDictionary *desc)
  78. {
  79. NSNumber *version = desc[SyphonServerDescriptionDictionaryVersionKey];
  80. if (!version)
  81. return LOG(LOG_WARNING, "Server description does not contain "
  82. "VersionKey");
  83. if (version.unsignedIntValue > 0)
  84. LOG(LOG_WARNING,
  85. "Got server description version %d, "
  86. "expected 0",
  87. version.unsignedIntValue);
  88. }
  89. static inline void check_description(syphon_t s, NSDictionary *desc)
  90. {
  91. NSArray *surfaces = desc[SyphonServerDescriptionSurfacesKey];
  92. if (!surfaces)
  93. return LOG(LOG_WARNING, "Server description does not contain "
  94. "SyphonServerDescriptionSurfacesKey");
  95. if (!surfaces.count)
  96. return LOG(LOG_WARNING, "Server description contains empty "
  97. "SyphonServerDescriptionSurfacesKey");
  98. for (NSDictionary *surface in surfaces) {
  99. NSString *type = surface[SyphonSurfaceType];
  100. if (type && [type isEqual:SyphonSurfaceTypeIOSurface])
  101. return;
  102. }
  103. NSString *surfaces_string = [NSString stringWithFormat:@"%@", surfaces];
  104. LOG(LOG_WARNING,
  105. "SyphonSurfaces does not contain"
  106. "'SyphonSurfaceTypeIOSurface': %s",
  107. surfaces_string.UTF8String);
  108. }
  109. static inline void handle_new_frame(syphon_t s, SyphonOBSClient *client)
  110. {
  111. IOSurfaceRef ref = [client newFrameImage];
  112. if (!ref)
  113. return;
  114. if (ref == s->ref) {
  115. CFRelease(ref);
  116. return;
  117. }
  118. IOSurfaceIncrementUseCount(ref);
  119. obs_enter_graphics();
  120. if (s->ref) {
  121. gs_texture_destroy(s->tex);
  122. IOSurfaceDecrementUseCount(s->ref);
  123. CFRelease(s->ref);
  124. }
  125. s->ref = ref;
  126. s->tex = gs_texture_create_from_iosurface(s->ref);
  127. s->width = gs_texture_get_width(s->tex);
  128. s->height = gs_texture_get_height(s->tex);
  129. obs_leave_graphics();
  130. }
  131. static void create_client(syphon_t s)
  132. {
  133. stop_client(s);
  134. if (!s->app_name.length && !s->name.length && !s->uuid.length)
  135. return;
  136. SyphonServerDirectory *ssd = [SyphonServerDirectory sharedDirectory];
  137. NSArray *servers = [ssd serversMatchingName:s->name appName:s->app_name];
  138. if (!servers.count)
  139. return;
  140. NSDictionary *desc = find_by_uuid(servers, s->uuid);
  141. if (!desc) {
  142. desc = servers[0];
  143. if (![s->uuid isEqualToString:desc[SyphonServerDescriptionUUIDKey]]) {
  144. s->uuid_changed = true;
  145. }
  146. }
  147. check_version(s, desc);
  148. check_description(s, desc);
  149. s->client = [[SyphonOBSClient alloc] initWithServerDescription:desc options:nil
  150. newFrameHandler:^(SyphonOBSClient *client) {
  151. handle_new_frame(s, client);
  152. }];
  153. s->active = true;
  154. }
  155. static inline bool load_syphon_settings(syphon_t s, obs_data_t *settings)
  156. {
  157. NSString *app_name = @(obs_data_get_string(settings, "app_name"));
  158. NSString *name = @(obs_data_get_string(settings, "name"));
  159. bool equal_names = [app_name isEqual:s->app_name] && [name isEqual:s->name];
  160. if (s->uuid_changed && equal_names)
  161. return false;
  162. NSString *uuid = @(obs_data_get_string(settings, "uuid"));
  163. if ([uuid isEqual:s->uuid] && equal_names)
  164. return false;
  165. s->app_name = app_name;
  166. s->name = name;
  167. s->uuid = uuid;
  168. s->uuid_changed = false;
  169. return true;
  170. }
  171. static inline void update_from_announce(syphon_t s, NSDictionary *info)
  172. {
  173. if (s->active)
  174. return;
  175. if (!info)
  176. return;
  177. NSString *app_name = info[SyphonServerDescriptionAppNameKey];
  178. NSString *name = info[SyphonServerDescriptionNameKey];
  179. NSString *uuid = info[SyphonServerDescriptionUUIDKey];
  180. if (![uuid isEqual:s->uuid] && !([app_name isEqual:s->app_name] && [name isEqual:s->name]))
  181. return;
  182. s->app_name = app_name;
  183. s->name = name;
  184. if (![s->uuid isEqualToString:uuid]) {
  185. s->uuid = uuid;
  186. s->uuid_changed = true;
  187. }
  188. create_client(s);
  189. }
  190. static inline void handle_announce(syphon_t s, NSNotification *note)
  191. {
  192. if (!note)
  193. return;
  194. update_from_announce(s, note.userInfo);
  195. update_properties(s);
  196. }
  197. static inline void update_from_retire(syphon_t s, NSDictionary *info)
  198. {
  199. if (!info)
  200. return;
  201. NSString *uuid = info[SyphonServerDescriptionUUIDKey];
  202. if (!uuid)
  203. return;
  204. if (![uuid isEqual:s->uuid])
  205. return;
  206. s->active = false;
  207. }
  208. static inline void handle_retire(syphon_t s, NSNotification *note)
  209. {
  210. if (!note)
  211. return;
  212. update_from_retire(s, note.userInfo);
  213. update_properties(s);
  214. }
  215. static inline gs_vertbuffer_t *create_vertbuffer()
  216. {
  217. struct gs_vb_data *vb_data = gs_vbdata_create();
  218. vb_data->num = 4;
  219. vb_data->points = bzalloc(sizeof(struct vec3) * 4);
  220. if (!vb_data->points)
  221. return NULL;
  222. vb_data->num_tex = 1;
  223. vb_data->tvarray = bzalloc(sizeof(struct gs_tvertarray));
  224. if (!vb_data->tvarray)
  225. goto fail_tvarray;
  226. vb_data->tvarray[0].width = 2;
  227. vb_data->tvarray[0].array = bzalloc(sizeof(struct vec2) * 4);
  228. if (!vb_data->tvarray[0].array)
  229. goto fail_array;
  230. gs_vertbuffer_t *vbuff = gs_vertexbuffer_create(vb_data, GS_DYNAMIC);
  231. if (vbuff)
  232. return vbuff;
  233. bfree(vb_data->tvarray[0].array);
  234. fail_array:
  235. bfree(vb_data->tvarray);
  236. fail_tvarray:
  237. bfree(vb_data->points);
  238. return NULL;
  239. }
  240. static inline bool init_obs_graphics_objects(syphon_t s)
  241. {
  242. struct gs_sampler_info info = {
  243. .filter = GS_FILTER_LINEAR,
  244. .address_u = GS_ADDRESS_CLAMP,
  245. .address_v = GS_ADDRESS_CLAMP,
  246. .address_w = GS_ADDRESS_CLAMP,
  247. .max_anisotropy = 1,
  248. };
  249. obs_enter_graphics();
  250. s->sampler = gs_samplerstate_create(&info);
  251. s->vertbuffer = create_vertbuffer();
  252. obs_leave_graphics();
  253. s->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT);
  254. return s->sampler != NULL && s->vertbuffer != NULL && s->effect != NULL;
  255. }
  256. static inline bool create_syphon_listeners(syphon_t s)
  257. {
  258. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  259. s->new_server_listener = [nc addObserverForName:SyphonServerAnnounceNotification object:nil
  260. queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
  261. handle_announce(s, note);
  262. }];
  263. s->retire_listener = [nc addObserverForName:SyphonServerRetireNotification object:nil
  264. queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
  265. handle_retire(s, note);
  266. }];
  267. return s->new_server_listener != nil && s->retire_listener != nil;
  268. }
  269. static inline void load_crop(syphon_t s, obs_data_t *settings)
  270. {
  271. s->crop = obs_data_get_bool(settings, "crop");
  272. #define LOAD_CROP(x) s->crop_rect.x = obs_data_get_double(settings, "crop." #x)
  273. LOAD_CROP(origin.x);
  274. LOAD_CROP(origin.y);
  275. LOAD_CROP(size.width);
  276. LOAD_CROP(size.height);
  277. #undef LOAD_CROP
  278. }
  279. static inline void syphon_destroy_internal(syphon_t s);
  280. static void *syphon_create_internal(obs_data_t *settings, obs_source_t *source)
  281. {
  282. syphon_t s = bzalloc(sizeof(struct syphon));
  283. if (!s)
  284. return s;
  285. s->source = source;
  286. if (!init_obs_graphics_objects(s)) {
  287. syphon_destroy_internal(s);
  288. return NULL;
  289. }
  290. if (!load_syphon_settings(s, settings)) {
  291. syphon_destroy_internal(s);
  292. return NULL;
  293. }
  294. if (!create_syphon_listeners(s)) {
  295. syphon_destroy_internal(s);
  296. return NULL;
  297. }
  298. create_client(s);
  299. load_crop(s, settings);
  300. s->allow_transparency = obs_data_get_bool(settings, "allow_transparency");
  301. return s;
  302. }
  303. static void *syphon_create(obs_data_t *settings, obs_source_t *source)
  304. {
  305. @autoreleasepool {
  306. return syphon_create_internal(settings, source);
  307. }
  308. }
  309. static inline void stop_listener(id listener)
  310. {
  311. if (!listener)
  312. return;
  313. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  314. [nc removeObserver:listener];
  315. }
  316. static inline void syphon_destroy_internal(syphon_t s)
  317. {
  318. stop_listener(s->new_server_listener);
  319. stop_listener(s->retire_listener);
  320. obs_enter_graphics();
  321. stop_client(s);
  322. if (s->sampler)
  323. gs_samplerstate_destroy(s->sampler);
  324. if (s->vertbuffer)
  325. gs_vertexbuffer_destroy(s->vertbuffer);
  326. obs_leave_graphics();
  327. bfree(s);
  328. }
  329. static void syphon_destroy(void *data)
  330. {
  331. @autoreleasepool {
  332. syphon_destroy_internal(data);
  333. }
  334. }
  335. static inline NSString *get_string(obs_data_t *settings, const char *name)
  336. {
  337. if (!settings)
  338. return nil;
  339. return @(obs_data_get_string(settings, name));
  340. }
  341. static inline void update_strings_from_context(syphon_t s, obs_data_t *settings, NSString **app, NSString **name,
  342. NSString **uuid)
  343. {
  344. if (!s || !s->uuid_changed)
  345. return;
  346. s->uuid_changed = false;
  347. *app = s->app_name;
  348. *name = s->name;
  349. *uuid = s->uuid;
  350. obs_data_set_string(settings, "app_name", s->app_name.UTF8String);
  351. obs_data_set_string(settings, "name", s->name.UTF8String);
  352. obs_data_set_string(settings, "uuid", s->uuid.UTF8String);
  353. }
  354. static inline void add_servers(syphon_t s, obs_property_t *list, obs_data_t *settings)
  355. {
  356. bool found_current = settings == NULL;
  357. NSString *set_app = get_string(settings, "app_name");
  358. NSString *set_name = get_string(settings, "name");
  359. NSString *set_uuid = get_string(settings, "uuid");
  360. update_strings_from_context(s, settings, &set_app, &set_name, &set_uuid);
  361. obs_property_list_add_string(list, "", "");
  362. NSArray *arr = [[SyphonServerDirectory sharedDirectory] servers];
  363. for (NSDictionary *server in arr) {
  364. NSString *app = server[SyphonServerDescriptionAppNameKey];
  365. NSString *name = server[SyphonServerDescriptionNameKey];
  366. NSString *uuid = server[SyphonServerDescriptionUUIDKey];
  367. NSString *serv = [NSString stringWithFormat:@"[%@] %@", app, name];
  368. obs_property_list_add_string(list, serv.UTF8String, uuid.UTF8String);
  369. if (!found_current)
  370. found_current = [uuid isEqual:set_uuid];
  371. }
  372. if (found_current || !set_uuid.length || !set_app.length)
  373. return;
  374. NSString *serv = [NSString stringWithFormat:@"[%@] %@", set_app, set_name];
  375. size_t idx = obs_property_list_add_string(list, serv.UTF8String, set_uuid.UTF8String);
  376. obs_property_list_item_disable(list, idx, true);
  377. }
  378. static bool servers_changed(obs_properties_t *props, obs_property_t *list, obs_data_t *settings)
  379. {
  380. @autoreleasepool {
  381. obs_property_list_clear(list);
  382. add_servers(obs_properties_get_param(props), list, settings);
  383. return true;
  384. }
  385. }
  386. static bool update_crop(obs_properties_t *props, obs_property_t *prop, obs_data_t *settings)
  387. {
  388. bool enabled = obs_data_get_bool(settings, "crop");
  389. #define LOAD_CROP(x) \
  390. prop = obs_properties_get(props, "crop." #x); \
  391. obs_property_set_enabled(prop, enabled);
  392. LOAD_CROP(origin.x);
  393. LOAD_CROP(origin.y);
  394. LOAD_CROP(size.width);
  395. LOAD_CROP(size.height);
  396. #undef LOAD_CROP
  397. return true;
  398. }
  399. static void show_syphon_license_internal(void)
  400. {
  401. char *path = obs_module_file("syphon_license.txt");
  402. if (!path)
  403. return;
  404. NSWorkspace *ws = [NSWorkspace sharedWorkspace];
  405. NSURL *url =
  406. [NSURL URLWithString:[@"file://" stringByAppendingString:[NSString stringWithCString:path
  407. encoding:NSUTF8StringEncoding]]];
  408. [ws openURL:url];
  409. bfree(path);
  410. }
  411. static bool show_syphon_license(obs_properties_t *props, obs_property_t *prop, void *data)
  412. {
  413. UNUSED_PARAMETER(props);
  414. UNUSED_PARAMETER(prop);
  415. UNUSED_PARAMETER(data);
  416. @autoreleasepool {
  417. show_syphon_license_internal();
  418. return false;
  419. }
  420. }
  421. static void syphon_release(void *param)
  422. {
  423. if (!param)
  424. return;
  425. obs_source_release(((syphon_t) param)->source);
  426. }
  427. static inline obs_properties_t *syphon_properties_internal(syphon_t s)
  428. {
  429. if (s && obs_source_get_ref(s->source) == NULL) {
  430. s = NULL;
  431. }
  432. obs_properties_t *props = obs_properties_create_param(s, syphon_release);
  433. obs_property_t *list =
  434. obs_properties_add_list(props, "uuid", obs_module_text("Source"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  435. obs_property_set_modified_callback(list, servers_changed);
  436. obs_properties_add_bool(props, "allow_transparency", obs_module_text("AllowTransparency"));
  437. obs_property_t *crop = obs_properties_add_bool(props, "crop", obs_module_text("Crop"));
  438. obs_property_set_modified_callback(crop, update_crop);
  439. #define LOAD_CROP(x) obs_properties_add_float(props, "crop." #x, obs_module_text("Crop." #x), 0., 4096.f, .5f);
  440. LOAD_CROP(origin.x);
  441. LOAD_CROP(origin.y);
  442. LOAD_CROP(size.width);
  443. LOAD_CROP(size.height);
  444. #undef LOAD_CROP
  445. obs_properties_add_button(props, "syphon license", obs_module_text("SyphonLicense"), show_syphon_license);
  446. return props;
  447. }
  448. static obs_properties_t *syphon_properties(void *data)
  449. {
  450. @autoreleasepool {
  451. return syphon_properties_internal(data);
  452. }
  453. }
  454. static inline void syphon_save_internal(syphon_t s, obs_data_t *settings)
  455. {
  456. if (!s->uuid_changed)
  457. return;
  458. obs_data_set_string(settings, "app_name", s->app_name.UTF8String);
  459. obs_data_set_string(settings, "name", s->name.UTF8String);
  460. obs_data_set_string(settings, "uuid", s->uuid.UTF8String);
  461. }
  462. static void syphon_save(void *data, obs_data_t *settings)
  463. {
  464. @autoreleasepool {
  465. syphon_save_internal(data, settings);
  466. }
  467. }
  468. static inline void build_sprite(struct gs_vb_data *data, float fcx, float fcy, float start_u, float end_u,
  469. float start_v, float end_v)
  470. {
  471. struct vec2 *tvarray = data->tvarray[0].array;
  472. vec3_set(data->points + 1, fcx, 0.0f, 0.0f);
  473. vec3_set(data->points + 2, 0.0f, fcy, 0.0f);
  474. vec3_set(data->points + 3, fcx, fcy, 0.0f);
  475. vec2_set(tvarray, start_u, start_v);
  476. vec2_set(tvarray + 1, end_u, start_v);
  477. vec2_set(tvarray + 2, start_u, end_v);
  478. vec2_set(tvarray + 3, end_u, end_v);
  479. }
  480. static inline void build_sprite_rect(struct gs_vb_data *data, float origin_x, float origin_y, float end_x, float end_y)
  481. {
  482. build_sprite(data, fabsf(end_x - origin_x), fabsf(end_y - origin_y), origin_x, end_x, origin_y, end_y);
  483. }
  484. static void syphon_video_tick(void *data, float seconds)
  485. {
  486. UNUSED_PARAMETER(seconds);
  487. syphon_t s = data;
  488. if (!s->tex)
  489. return;
  490. static const CGRect null_crop = {{0.f}};
  491. const CGRect *crop = &null_crop;
  492. if (s->crop)
  493. crop = &s->crop_rect;
  494. obs_enter_graphics();
  495. build_sprite_rect(gs_vertexbuffer_get_data(s->vertbuffer), (float) crop->origin.x,
  496. s->height - (float) crop->origin.y, s->width - (float) crop->size.width,
  497. (float) crop->size.height);
  498. obs_leave_graphics();
  499. }
  500. static void syphon_video_render(void *data, gs_effect_t *effect)
  501. {
  502. UNUSED_PARAMETER(effect);
  503. syphon_t s = data;
  504. if (!s->tex)
  505. return;
  506. gs_vertexbuffer_flush(s->vertbuffer);
  507. gs_load_vertexbuffer(s->vertbuffer);
  508. gs_load_indexbuffer(NULL);
  509. gs_load_samplerstate(s->sampler, 0);
  510. const char *tech_name = s->allow_transparency ? "Draw" : "DrawOpaque";
  511. gs_technique_t *tech = gs_effect_get_technique(s->effect, tech_name);
  512. gs_effect_set_texture(gs_effect_get_param_by_name(s->effect, "image"), s->tex);
  513. gs_technique_begin(tech);
  514. gs_technique_begin_pass(tech, 0);
  515. gs_draw(GS_TRISTRIP, 0, 4);
  516. gs_technique_end_pass(tech);
  517. gs_technique_end(tech);
  518. }
  519. static uint32_t syphon_get_width(void *data)
  520. {
  521. syphon_t s = (syphon_t) data;
  522. if (!s->crop)
  523. return s->width;
  524. int32_t width = s->width - (int) s->crop_rect.origin.x - (int) s->crop_rect.size.width;
  525. return MAX(0, width);
  526. }
  527. static uint32_t syphon_get_height(void *data)
  528. {
  529. syphon_t s = (syphon_t) data;
  530. if (!s->crop)
  531. return s->height;
  532. int32_t height = s->height - (int) s->crop_rect.origin.y - (int) s->crop_rect.size.height;
  533. return MAX(0, height);
  534. }
  535. static inline bool update_syphon(syphon_t s, obs_data_t *settings)
  536. {
  537. NSArray *arr = [[SyphonServerDirectory sharedDirectory] servers];
  538. if (!load_syphon_settings(s, settings))
  539. return false;
  540. NSDictionary *dict = find_by_uuid(arr, s->uuid);
  541. if (dict) {
  542. NSString *app = dict[SyphonServerDescriptionAppNameKey];
  543. NSString *name = dict[SyphonServerDescriptionNameKey];
  544. obs_data_set_string(settings, "app_name", app.UTF8String);
  545. obs_data_set_string(settings, "name", name.UTF8String);
  546. load_syphon_settings(s, settings);
  547. } else if (!dict && !s->uuid.length) {
  548. obs_data_set_string(settings, "app_name", "");
  549. obs_data_set_string(settings, "name", "");
  550. load_syphon_settings(s, settings);
  551. }
  552. return true;
  553. }
  554. static void syphon_update_internal(syphon_t s, obs_data_t *settings)
  555. {
  556. s->allow_transparency = obs_data_get_bool(settings, "allow_transparency");
  557. load_crop(s, settings);
  558. if (update_syphon(s, settings))
  559. create_client(s);
  560. }
  561. static void syphon_update(void *data, obs_data_t *settings)
  562. {
  563. @autoreleasepool {
  564. syphon_update_internal(data, settings);
  565. }
  566. }
  567. struct obs_source_info syphon_info = {
  568. .id = "syphon-input",
  569. .type = OBS_SOURCE_TYPE_INPUT,
  570. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE,
  571. .get_name = syphon_get_name,
  572. .create = syphon_create,
  573. .destroy = syphon_destroy,
  574. .video_render = syphon_video_render,
  575. .video_tick = syphon_video_tick,
  576. .get_properties = syphon_properties,
  577. .get_width = syphon_get_width,
  578. .get_height = syphon_get_height,
  579. .update = syphon_update,
  580. .save = syphon_save,
  581. .icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
  582. };