syphon.m 20 KB

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