syphon.m 19 KB

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