syphon.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  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. if (gs_get_device_type() == GS_DEVICE_OPENGL) {
  253. s->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT);
  254. } else {
  255. s->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
  256. }
  257. obs_leave_graphics();
  258. return s->sampler != NULL && s->vertbuffer != NULL && s->effect != NULL;
  259. }
  260. static inline bool create_syphon_listeners(syphon_t s)
  261. {
  262. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  263. s->new_server_listener = [nc addObserverForName:SyphonServerAnnounceNotification object:nil
  264. queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
  265. handle_announce(s, note);
  266. }];
  267. s->retire_listener = [nc addObserverForName:SyphonServerRetireNotification object:nil
  268. queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
  269. handle_retire(s, note);
  270. }];
  271. return s->new_server_listener != nil && s->retire_listener != nil;
  272. }
  273. static inline void load_crop(syphon_t s, obs_data_t *settings)
  274. {
  275. s->crop = obs_data_get_bool(settings, "crop");
  276. #define LOAD_CROP(x) s->crop_rect.x = obs_data_get_double(settings, "crop." #x)
  277. LOAD_CROP(origin.x);
  278. LOAD_CROP(origin.y);
  279. LOAD_CROP(size.width);
  280. LOAD_CROP(size.height);
  281. #undef LOAD_CROP
  282. }
  283. static inline void syphon_destroy_internal(syphon_t s);
  284. static void *syphon_create_internal(obs_data_t *settings, obs_source_t *source)
  285. {
  286. syphon_t s = bzalloc(sizeof(struct syphon));
  287. if (!s)
  288. return s;
  289. s->source = source;
  290. if (!init_obs_graphics_objects(s)) {
  291. syphon_destroy_internal(s);
  292. return NULL;
  293. }
  294. if (!load_syphon_settings(s, settings)) {
  295. syphon_destroy_internal(s);
  296. return NULL;
  297. }
  298. if (!create_syphon_listeners(s)) {
  299. syphon_destroy_internal(s);
  300. return NULL;
  301. }
  302. create_client(s);
  303. load_crop(s, settings);
  304. s->allow_transparency = obs_data_get_bool(settings, "allow_transparency");
  305. return s;
  306. }
  307. static void *syphon_create(obs_data_t *settings, obs_source_t *source)
  308. {
  309. @autoreleasepool {
  310. return syphon_create_internal(settings, source);
  311. }
  312. }
  313. static inline void stop_listener(id listener)
  314. {
  315. if (!listener)
  316. return;
  317. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  318. [nc removeObserver:listener];
  319. }
  320. static inline void syphon_destroy_internal(syphon_t s)
  321. {
  322. stop_listener(s->new_server_listener);
  323. stop_listener(s->retire_listener);
  324. obs_enter_graphics();
  325. stop_client(s);
  326. if (s->sampler)
  327. gs_samplerstate_destroy(s->sampler);
  328. if (s->vertbuffer)
  329. gs_vertexbuffer_destroy(s->vertbuffer);
  330. obs_leave_graphics();
  331. bfree(s);
  332. }
  333. static void syphon_destroy(void *data)
  334. {
  335. @autoreleasepool {
  336. syphon_destroy_internal(data);
  337. }
  338. }
  339. static inline NSString *get_string(obs_data_t *settings, const char *name)
  340. {
  341. if (!settings)
  342. return nil;
  343. return @(obs_data_get_string(settings, name));
  344. }
  345. static inline void update_strings_from_context(syphon_t s, obs_data_t *settings, NSString **app, NSString **name,
  346. NSString **uuid)
  347. {
  348. if (!s || !s->uuid_changed)
  349. return;
  350. s->uuid_changed = false;
  351. *app = s->app_name;
  352. *name = s->name;
  353. *uuid = s->uuid;
  354. obs_data_set_string(settings, "app_name", s->app_name.UTF8String);
  355. obs_data_set_string(settings, "name", s->name.UTF8String);
  356. obs_data_set_string(settings, "uuid", s->uuid.UTF8String);
  357. }
  358. static inline void add_servers(syphon_t s, obs_property_t *list, obs_data_t *settings)
  359. {
  360. bool found_current = settings == NULL;
  361. NSString *set_app = get_string(settings, "app_name");
  362. NSString *set_name = get_string(settings, "name");
  363. NSString *set_uuid = get_string(settings, "uuid");
  364. update_strings_from_context(s, settings, &set_app, &set_name, &set_uuid);
  365. obs_property_list_add_string(list, "", "");
  366. NSArray *arr = [[SyphonServerDirectory sharedDirectory] servers];
  367. for (NSDictionary *server in arr) {
  368. NSString *app = server[SyphonServerDescriptionAppNameKey];
  369. NSString *name = server[SyphonServerDescriptionNameKey];
  370. NSString *uuid = server[SyphonServerDescriptionUUIDKey];
  371. NSString *serv = [NSString stringWithFormat:@"[%@] %@", app, name];
  372. obs_property_list_add_string(list, serv.UTF8String, uuid.UTF8String);
  373. if (!found_current)
  374. found_current = [uuid isEqual:set_uuid];
  375. }
  376. if (found_current || !set_uuid.length || !set_app.length)
  377. return;
  378. NSString *serv = [NSString stringWithFormat:@"[%@] %@", set_app, set_name];
  379. size_t idx = obs_property_list_add_string(list, serv.UTF8String, set_uuid.UTF8String);
  380. obs_property_list_item_disable(list, idx, true);
  381. }
  382. static bool servers_changed(obs_properties_t *props, obs_property_t *list, obs_data_t *settings)
  383. {
  384. @autoreleasepool {
  385. obs_property_list_clear(list);
  386. add_servers(obs_properties_get_param(props), list, settings);
  387. return true;
  388. }
  389. }
  390. static bool update_crop(obs_properties_t *props, obs_property_t *prop, obs_data_t *settings)
  391. {
  392. bool enabled = obs_data_get_bool(settings, "crop");
  393. #define LOAD_CROP(x) \
  394. prop = obs_properties_get(props, "crop." #x); \
  395. obs_property_set_enabled(prop, enabled);
  396. LOAD_CROP(origin.x);
  397. LOAD_CROP(origin.y);
  398. LOAD_CROP(size.width);
  399. LOAD_CROP(size.height);
  400. #undef LOAD_CROP
  401. return true;
  402. }
  403. static void show_syphon_license_internal(void)
  404. {
  405. char *path = obs_module_file("syphon_license.txt");
  406. if (!path)
  407. return;
  408. NSWorkspace *ws = [NSWorkspace sharedWorkspace];
  409. NSURL *url =
  410. [NSURL URLWithString:[@"file://" stringByAppendingString:[NSString stringWithCString:path
  411. encoding:NSUTF8StringEncoding]]];
  412. [ws openURL:url];
  413. bfree(path);
  414. }
  415. static bool show_syphon_license(obs_properties_t *props, obs_property_t *prop, void *data)
  416. {
  417. UNUSED_PARAMETER(props);
  418. UNUSED_PARAMETER(prop);
  419. UNUSED_PARAMETER(data);
  420. @autoreleasepool {
  421. show_syphon_license_internal();
  422. return false;
  423. }
  424. }
  425. static void syphon_release(void *param)
  426. {
  427. if (!param)
  428. return;
  429. obs_source_release(((syphon_t) param)->source);
  430. }
  431. static inline obs_properties_t *syphon_properties_internal(syphon_t s)
  432. {
  433. if (s && obs_source_get_ref(s->source) == NULL) {
  434. s = NULL;
  435. }
  436. obs_properties_t *props = obs_properties_create_param(s, syphon_release);
  437. obs_property_t *list =
  438. obs_properties_add_list(props, "uuid", obs_module_text("Source"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  439. obs_property_set_modified_callback(list, servers_changed);
  440. obs_properties_add_bool(props, "allow_transparency", obs_module_text("AllowTransparency"));
  441. obs_property_t *crop = obs_properties_add_bool(props, "crop", obs_module_text("Crop"));
  442. obs_property_set_modified_callback(crop, update_crop);
  443. #define LOAD_CROP(x) obs_properties_add_float(props, "crop." #x, obs_module_text("Crop." #x), 0., 4096.f, .5f);
  444. LOAD_CROP(origin.x);
  445. LOAD_CROP(origin.y);
  446. LOAD_CROP(size.width);
  447. LOAD_CROP(size.height);
  448. #undef LOAD_CROP
  449. obs_properties_add_button(props, "syphon license", obs_module_text("SyphonLicense"), show_syphon_license);
  450. return props;
  451. }
  452. static obs_properties_t *syphon_properties(void *data)
  453. {
  454. @autoreleasepool {
  455. return syphon_properties_internal(data);
  456. }
  457. }
  458. static inline void syphon_save_internal(syphon_t s, obs_data_t *settings)
  459. {
  460. if (!s->uuid_changed)
  461. return;
  462. obs_data_set_string(settings, "app_name", s->app_name.UTF8String);
  463. obs_data_set_string(settings, "name", s->name.UTF8String);
  464. obs_data_set_string(settings, "uuid", s->uuid.UTF8String);
  465. }
  466. static void syphon_save(void *data, obs_data_t *settings)
  467. {
  468. @autoreleasepool {
  469. syphon_save_internal(data, settings);
  470. }
  471. }
  472. static inline void build_sprite(struct gs_vb_data *data, float fcx, float fcy, float start_u, float end_u,
  473. float start_v, float end_v)
  474. {
  475. struct vec2 *tvarray = data->tvarray[0].array;
  476. vec3_set(data->points + 1, fcx, 0.0f, 0.0f);
  477. vec3_set(data->points + 2, 0.0f, fcy, 0.0f);
  478. vec3_set(data->points + 3, fcx, fcy, 0.0f);
  479. vec2_set(tvarray, start_u, start_v);
  480. vec2_set(tvarray + 1, end_u, start_v);
  481. vec2_set(tvarray + 2, start_u, end_v);
  482. vec2_set(tvarray + 3, end_u, end_v);
  483. }
  484. static inline void build_sprite_rect(struct gs_vb_data *data, float origin_x, float origin_y, float end_x, float end_y)
  485. {
  486. build_sprite(data, fabsf(end_x - origin_x), fabsf(end_y - origin_y), origin_x, end_x, origin_y, end_y);
  487. }
  488. static void syphon_video_tick(void *data, float seconds)
  489. {
  490. UNUSED_PARAMETER(seconds);
  491. syphon_t s = data;
  492. if (!s->tex)
  493. return;
  494. static const CGRect null_crop = {{0.f}};
  495. const CGRect *crop = &null_crop;
  496. if (s->crop)
  497. crop = &s->crop_rect;
  498. obs_enter_graphics();
  499. build_sprite_rect(gs_vertexbuffer_get_data(s->vertbuffer), (float) crop->origin.x,
  500. s->height - (float) crop->origin.y, s->width - (float) crop->size.width,
  501. (float) crop->size.height);
  502. obs_leave_graphics();
  503. }
  504. static void syphon_video_render(void *data, gs_effect_t *effect)
  505. {
  506. UNUSED_PARAMETER(effect);
  507. syphon_t s = data;
  508. if (!s->tex)
  509. return;
  510. gs_vertexbuffer_flush(s->vertbuffer);
  511. gs_load_vertexbuffer(s->vertbuffer);
  512. gs_load_indexbuffer(NULL);
  513. gs_load_samplerstate(s->sampler, 0);
  514. const char *tech_name = s->allow_transparency ? "Draw" : "DrawOpaque";
  515. gs_technique_t *tech = gs_effect_get_technique(s->effect, tech_name);
  516. gs_effect_set_texture(gs_effect_get_param_by_name(s->effect, "image"), s->tex);
  517. gs_technique_begin(tech);
  518. gs_technique_begin_pass(tech, 0);
  519. gs_draw(GS_TRISTRIP, 0, 4);
  520. gs_technique_end_pass(tech);
  521. gs_technique_end(tech);
  522. }
  523. static uint32_t syphon_get_width(void *data)
  524. {
  525. syphon_t s = (syphon_t) data;
  526. if (!s->crop)
  527. return s->width;
  528. int32_t width = s->width - (int) s->crop_rect.origin.x - (int) s->crop_rect.size.width;
  529. return MAX(0, width);
  530. }
  531. static uint32_t syphon_get_height(void *data)
  532. {
  533. syphon_t s = (syphon_t) data;
  534. if (!s->crop)
  535. return s->height;
  536. int32_t height = s->height - (int) s->crop_rect.origin.y - (int) s->crop_rect.size.height;
  537. return MAX(0, height);
  538. }
  539. static inline bool update_syphon(syphon_t s, obs_data_t *settings)
  540. {
  541. NSArray *arr = [[SyphonServerDirectory sharedDirectory] servers];
  542. if (!load_syphon_settings(s, settings))
  543. return false;
  544. NSDictionary *dict = find_by_uuid(arr, s->uuid);
  545. if (dict) {
  546. NSString *app = dict[SyphonServerDescriptionAppNameKey];
  547. NSString *name = dict[SyphonServerDescriptionNameKey];
  548. obs_data_set_string(settings, "app_name", app.UTF8String);
  549. obs_data_set_string(settings, "name", name.UTF8String);
  550. load_syphon_settings(s, settings);
  551. } else if (!dict && !s->uuid.length) {
  552. obs_data_set_string(settings, "app_name", "");
  553. obs_data_set_string(settings, "name", "");
  554. load_syphon_settings(s, settings);
  555. }
  556. return true;
  557. }
  558. static void syphon_update_internal(syphon_t s, obs_data_t *settings)
  559. {
  560. s->allow_transparency = obs_data_get_bool(settings, "allow_transparency");
  561. load_crop(s, settings);
  562. if (update_syphon(s, settings))
  563. create_client(s);
  564. }
  565. static void syphon_update(void *data, obs_data_t *settings)
  566. {
  567. @autoreleasepool {
  568. syphon_update_internal(data, settings);
  569. }
  570. }
  571. struct obs_source_info syphon_info = {
  572. .id = "syphon-input",
  573. .type = OBS_SOURCE_TYPE_INPUT,
  574. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE,
  575. .get_name = syphon_get_name,
  576. .create = syphon_create,
  577. .destroy = syphon_destroy,
  578. .video_render = syphon_video_render,
  579. .video_tick = syphon_video_tick,
  580. .get_properties = syphon_properties,
  581. .get_width = syphon_get_width,
  582. .get_height = syphon_get_height,
  583. .update = syphon_update,
  584. .save = syphon_save,
  585. .icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
  586. };