obs-cocoa.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. /******************************************************************************
  2. Copyright (C) 2013 by Ruwen Hahn <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "util/platform.h"
  15. #include "util/dstr.h"
  16. #include "obs.h"
  17. #include "obs-internal.h"
  18. #include <unistd.h>
  19. #include <sys/types.h>
  20. #include <sys/sysctl.h>
  21. #include <Carbon/Carbon.h>
  22. #include <IOKit/hid/IOHIDDevice.h>
  23. #include <IOKit/hid/IOHIDManager.h>
  24. #import <AppKit/AppKit.h>
  25. bool is_in_bundle()
  26. {
  27. NSRunningApplication *app = [NSRunningApplication currentApplication];
  28. return [app bundleIdentifier] != nil;
  29. }
  30. const char *get_module_extension(void)
  31. {
  32. return "";
  33. }
  34. void add_default_module_paths(void)
  35. {
  36. struct dstr plugin_path;
  37. dstr_init_move_array(&plugin_path, os_get_executable_path_ptr(""));
  38. dstr_cat(&plugin_path, "../PlugIns");
  39. char *abs_plugin_path = os_get_abs_path_ptr(plugin_path.array);
  40. if (abs_plugin_path != NULL) {
  41. dstr_move_array(&plugin_path, abs_plugin_path);
  42. struct dstr plugin_data;
  43. dstr_init_copy_dstr(&plugin_data, &plugin_path);
  44. dstr_cat(&plugin_path, "/%module%.plugin/Contents/MacOS/");
  45. dstr_cat(&plugin_data, "/%module%.plugin/Contents/Resources/");
  46. obs_add_module_path(plugin_path.array, plugin_data.array);
  47. dstr_free(&plugin_data);
  48. }
  49. dstr_free(&plugin_path);
  50. }
  51. char *find_libobs_data_file(const char *file)
  52. {
  53. struct dstr path;
  54. if (is_in_bundle()) {
  55. NSBundle *frameworkBundle = [NSBundle
  56. bundleWithIdentifier:@"com.obsproject.libobs"];
  57. NSURL *bundleURL = [frameworkBundle bundleURL];
  58. NSURL *libobsDataURL =
  59. [bundleURL URLByAppendingPathComponent:@"Resources/"];
  60. const char *libobsDataPath = [[libobsDataURL path]
  61. cStringUsingEncoding:NSUTF8StringEncoding];
  62. dstr_init_copy(&path, libobsDataPath);
  63. dstr_cat(&path, "/");
  64. } else {
  65. dstr_init_copy(&path, OBS_INSTALL_DATA_PATH "/libobs/");
  66. }
  67. dstr_cat(&path, file);
  68. return path.array;
  69. }
  70. static void log_processor_name(void)
  71. {
  72. char *name = NULL;
  73. size_t size;
  74. int ret;
  75. ret = sysctlbyname("machdep.cpu.brand_string", NULL, &size, NULL, 0);
  76. if (ret != 0)
  77. return;
  78. name = malloc(size);
  79. ret = sysctlbyname("machdep.cpu.brand_string", name, &size, NULL, 0);
  80. if (ret == 0)
  81. blog(LOG_INFO, "CPU Name: %s", name);
  82. free(name);
  83. }
  84. static void log_processor_speed(void)
  85. {
  86. size_t size;
  87. long long freq;
  88. int ret;
  89. size = sizeof(freq);
  90. ret = sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0);
  91. if (ret == 0)
  92. blog(LOG_INFO, "CPU Speed: %lldMHz", freq / 1000000);
  93. }
  94. static void log_processor_cores(void)
  95. {
  96. blog(LOG_INFO, "Physical Cores: %d, Logical Cores: %d",
  97. os_get_physical_cores(), os_get_logical_cores());
  98. }
  99. static void log_available_memory(void)
  100. {
  101. size_t size;
  102. long long memory_available;
  103. int ret;
  104. size = sizeof(memory_available);
  105. ret = sysctlbyname("hw.memsize", &memory_available, &size, NULL, 0);
  106. if (ret == 0)
  107. blog(LOG_INFO, "Physical Memory: %lldMB Total",
  108. memory_available / 1024 / 1024);
  109. }
  110. static void log_os(void)
  111. {
  112. NSProcessInfo *pi = [NSProcessInfo processInfo];
  113. blog(LOG_INFO, "OS Name: macOS");
  114. blog(LOG_INFO, "OS Version: %s",
  115. [[pi operatingSystemVersionString] UTF8String]);
  116. }
  117. static void log_kernel_version(void)
  118. {
  119. char kernel_version[1024];
  120. size_t size = sizeof(kernel_version);
  121. int ret;
  122. ret = sysctlbyname("kern.osrelease", kernel_version, &size, NULL, 0);
  123. if (ret == 0)
  124. blog(LOG_INFO, "Kernel Version: %s", kernel_version);
  125. }
  126. void log_system_info(void)
  127. {
  128. log_processor_name();
  129. log_processor_speed();
  130. log_processor_cores();
  131. log_available_memory();
  132. log_os();
  133. log_kernel_version();
  134. }
  135. static bool dstr_from_cfstring(struct dstr *str, CFStringRef ref)
  136. {
  137. CFIndex length = CFStringGetLength(ref);
  138. CFIndex max_size = CFStringGetMaximumSizeForEncoding(
  139. length, kCFStringEncodingUTF8);
  140. dstr_reserve(str, max_size);
  141. if (!CFStringGetCString(ref, str->array, max_size,
  142. kCFStringEncodingUTF8))
  143. return false;
  144. str->len = strlen(str->array);
  145. return true;
  146. }
  147. struct obs_hotkeys_platform {
  148. volatile long refs;
  149. CFTypeRef monitor;
  150. bool is_key_down[OBS_KEY_LAST_VALUE];
  151. TISInputSourceRef tis;
  152. CFDataRef layout_data;
  153. UCKeyboardLayout *layout;
  154. };
  155. static void hotkeys_retain(struct obs_hotkeys_platform *plat)
  156. {
  157. os_atomic_inc_long(&plat->refs);
  158. }
  159. static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat);
  160. static void hotkeys_release(struct obs_hotkeys_platform *plat)
  161. {
  162. if (os_atomic_dec_long(&plat->refs) == -1)
  163. free_hotkeys_platform(plat);
  164. }
  165. #define INVALID_KEY 0xff
  166. #pragma GCC diagnostic ignored "-Winitializer-overrides"
  167. static const int virtual_keys[] = {
  168. [0 ... OBS_KEY_LAST_VALUE] = INVALID_KEY,
  169. [OBS_KEY_A] = kVK_ANSI_A,
  170. [OBS_KEY_B] = kVK_ANSI_B,
  171. [OBS_KEY_C] = kVK_ANSI_C,
  172. [OBS_KEY_D] = kVK_ANSI_D,
  173. [OBS_KEY_E] = kVK_ANSI_E,
  174. [OBS_KEY_F] = kVK_ANSI_F,
  175. [OBS_KEY_G] = kVK_ANSI_G,
  176. [OBS_KEY_H] = kVK_ANSI_H,
  177. [OBS_KEY_I] = kVK_ANSI_I,
  178. [OBS_KEY_J] = kVK_ANSI_J,
  179. [OBS_KEY_K] = kVK_ANSI_K,
  180. [OBS_KEY_L] = kVK_ANSI_L,
  181. [OBS_KEY_M] = kVK_ANSI_M,
  182. [OBS_KEY_N] = kVK_ANSI_N,
  183. [OBS_KEY_O] = kVK_ANSI_O,
  184. [OBS_KEY_P] = kVK_ANSI_P,
  185. [OBS_KEY_Q] = kVK_ANSI_Q,
  186. [OBS_KEY_R] = kVK_ANSI_R,
  187. [OBS_KEY_S] = kVK_ANSI_S,
  188. [OBS_KEY_T] = kVK_ANSI_T,
  189. [OBS_KEY_U] = kVK_ANSI_U,
  190. [OBS_KEY_V] = kVK_ANSI_V,
  191. [OBS_KEY_W] = kVK_ANSI_W,
  192. [OBS_KEY_X] = kVK_ANSI_X,
  193. [OBS_KEY_Y] = kVK_ANSI_Y,
  194. [OBS_KEY_Z] = kVK_ANSI_Z,
  195. [OBS_KEY_1] = kVK_ANSI_1,
  196. [OBS_KEY_2] = kVK_ANSI_2,
  197. [OBS_KEY_3] = kVK_ANSI_3,
  198. [OBS_KEY_4] = kVK_ANSI_4,
  199. [OBS_KEY_5] = kVK_ANSI_5,
  200. [OBS_KEY_6] = kVK_ANSI_6,
  201. [OBS_KEY_7] = kVK_ANSI_7,
  202. [OBS_KEY_8] = kVK_ANSI_8,
  203. [OBS_KEY_9] = kVK_ANSI_9,
  204. [OBS_KEY_0] = kVK_ANSI_0,
  205. [OBS_KEY_RETURN] = kVK_Return,
  206. [OBS_KEY_ESCAPE] = kVK_Escape,
  207. [OBS_KEY_BACKSPACE] = kVK_Delete,
  208. [OBS_KEY_TAB] = kVK_Tab,
  209. [OBS_KEY_SPACE] = kVK_Space,
  210. [OBS_KEY_MINUS] = kVK_ANSI_Minus,
  211. [OBS_KEY_EQUAL] = kVK_ANSI_Equal,
  212. [OBS_KEY_BRACKETLEFT] = kVK_ANSI_LeftBracket,
  213. [OBS_KEY_BRACKETRIGHT] = kVK_ANSI_RightBracket,
  214. [OBS_KEY_BACKSLASH] = kVK_ANSI_Backslash,
  215. [OBS_KEY_SEMICOLON] = kVK_ANSI_Semicolon,
  216. [OBS_KEY_QUOTE] = kVK_ANSI_Quote,
  217. [OBS_KEY_DEAD_GRAVE] = kVK_ANSI_Grave,
  218. [OBS_KEY_COMMA] = kVK_ANSI_Comma,
  219. [OBS_KEY_PERIOD] = kVK_ANSI_Period,
  220. [OBS_KEY_SLASH] = kVK_ANSI_Slash,
  221. [OBS_KEY_CAPSLOCK] = kVK_CapsLock,
  222. [OBS_KEY_SECTION] = kVK_ISO_Section,
  223. [OBS_KEY_F1] = kVK_F1,
  224. [OBS_KEY_F2] = kVK_F2,
  225. [OBS_KEY_F3] = kVK_F3,
  226. [OBS_KEY_F4] = kVK_F4,
  227. [OBS_KEY_F5] = kVK_F5,
  228. [OBS_KEY_F6] = kVK_F6,
  229. [OBS_KEY_F7] = kVK_F7,
  230. [OBS_KEY_F8] = kVK_F8,
  231. [OBS_KEY_F9] = kVK_F9,
  232. [OBS_KEY_F10] = kVK_F10,
  233. [OBS_KEY_F11] = kVK_F11,
  234. [OBS_KEY_F12] = kVK_F12,
  235. [OBS_KEY_HELP] = kVK_Help,
  236. [OBS_KEY_HOME] = kVK_Home,
  237. [OBS_KEY_PAGEUP] = kVK_PageUp,
  238. [OBS_KEY_DELETE] = kVK_ForwardDelete,
  239. [OBS_KEY_END] = kVK_End,
  240. [OBS_KEY_PAGEDOWN] = kVK_PageDown,
  241. [OBS_KEY_RIGHT] = kVK_RightArrow,
  242. [OBS_KEY_LEFT] = kVK_LeftArrow,
  243. [OBS_KEY_DOWN] = kVK_DownArrow,
  244. [OBS_KEY_UP] = kVK_UpArrow,
  245. [OBS_KEY_CLEAR] = kVK_ANSI_KeypadClear,
  246. [OBS_KEY_NUMSLASH] = kVK_ANSI_KeypadDivide,
  247. [OBS_KEY_NUMASTERISK] = kVK_ANSI_KeypadMultiply,
  248. [OBS_KEY_NUMMINUS] = kVK_ANSI_KeypadMinus,
  249. [OBS_KEY_NUMPLUS] = kVK_ANSI_KeypadPlus,
  250. [OBS_KEY_ENTER] = kVK_ANSI_KeypadEnter,
  251. [OBS_KEY_NUM1] = kVK_ANSI_Keypad1,
  252. [OBS_KEY_NUM2] = kVK_ANSI_Keypad2,
  253. [OBS_KEY_NUM3] = kVK_ANSI_Keypad3,
  254. [OBS_KEY_NUM4] = kVK_ANSI_Keypad4,
  255. [OBS_KEY_NUM5] = kVK_ANSI_Keypad5,
  256. [OBS_KEY_NUM6] = kVK_ANSI_Keypad6,
  257. [OBS_KEY_NUM7] = kVK_ANSI_Keypad7,
  258. [OBS_KEY_NUM8] = kVK_ANSI_Keypad8,
  259. [OBS_KEY_NUM9] = kVK_ANSI_Keypad9,
  260. [OBS_KEY_NUM0] = kVK_ANSI_Keypad0,
  261. [OBS_KEY_NUMPERIOD] = kVK_ANSI_KeypadDecimal,
  262. [OBS_KEY_NUMEQUAL] = kVK_ANSI_KeypadEquals,
  263. [OBS_KEY_F13] = kVK_F13,
  264. [OBS_KEY_F14] = kVK_F14,
  265. [OBS_KEY_F15] = kVK_F15,
  266. [OBS_KEY_F16] = kVK_F16,
  267. [OBS_KEY_F17] = kVK_F17,
  268. [OBS_KEY_F18] = kVK_F18,
  269. [OBS_KEY_F19] = kVK_F19,
  270. [OBS_KEY_F20] = kVK_F20,
  271. [OBS_KEY_CONTROL] = kVK_Control,
  272. [OBS_KEY_SHIFT] = kVK_Shift,
  273. [OBS_KEY_ALT] = kVK_Option,
  274. [OBS_KEY_META] = kVK_Command,
  275. [OBS_KEY_CONTROL] = kVK_RightControl,
  276. };
  277. int obs_key_to_virtual_key(obs_key_t code)
  278. {
  279. return virtual_keys[code];
  280. }
  281. obs_key_t obs_key_from_virtual_key(int code)
  282. {
  283. if (code == kVK_RightShift)
  284. return OBS_KEY_SHIFT;
  285. if (code == kVK_RightOption)
  286. return OBS_KEY_ALT;
  287. if (code == kVK_RightCommand)
  288. return OBS_KEY_META;
  289. if (code == kVK_RightControl)
  290. return OBS_KEY_META;
  291. for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) {
  292. if (virtual_keys[i] == code) {
  293. return i;
  294. }
  295. }
  296. return OBS_KEY_NONE;
  297. }
  298. static bool localized_key_to_str(obs_key_t key, struct dstr *str)
  299. {
  300. #define MAP_KEY(k, s) \
  301. case k: \
  302. dstr_copy(str, obs_get_hotkey_translation(k, s)); \
  303. return true
  304. #define MAP_BUTTON(i) \
  305. case OBS_KEY_MOUSE##i: \
  306. dstr_copy(str, obs_get_hotkey_translation(key, "Mouse " #i)); \
  307. return true
  308. switch (key) {
  309. MAP_KEY(OBS_KEY_SPACE, "Space");
  310. MAP_KEY(OBS_KEY_NUMEQUAL, "= (Keypad)");
  311. MAP_KEY(OBS_KEY_NUMASTERISK, "* (Keypad)");
  312. MAP_KEY(OBS_KEY_NUMPLUS, "+ (Keypad)");
  313. MAP_KEY(OBS_KEY_NUMMINUS, "- (Keypad)");
  314. MAP_KEY(OBS_KEY_NUMPERIOD, ". (Keypad)");
  315. MAP_KEY(OBS_KEY_NUMSLASH, "/ (Keypad)");
  316. MAP_KEY(OBS_KEY_NUM0, "0 (Keypad)");
  317. MAP_KEY(OBS_KEY_NUM1, "1 (Keypad)");
  318. MAP_KEY(OBS_KEY_NUM2, "2 (Keypad)");
  319. MAP_KEY(OBS_KEY_NUM3, "3 (Keypad)");
  320. MAP_KEY(OBS_KEY_NUM4, "4 (Keypad)");
  321. MAP_KEY(OBS_KEY_NUM5, "5 (Keypad)");
  322. MAP_KEY(OBS_KEY_NUM6, "6 (Keypad)");
  323. MAP_KEY(OBS_KEY_NUM7, "7 (Keypad)");
  324. MAP_KEY(OBS_KEY_NUM8, "8 (Keypad)");
  325. MAP_KEY(OBS_KEY_NUM9, "9 (Keypad)");
  326. MAP_BUTTON(1);
  327. MAP_BUTTON(2);
  328. MAP_BUTTON(3);
  329. MAP_BUTTON(4);
  330. MAP_BUTTON(5);
  331. MAP_BUTTON(6);
  332. MAP_BUTTON(7);
  333. MAP_BUTTON(8);
  334. MAP_BUTTON(9);
  335. MAP_BUTTON(10);
  336. MAP_BUTTON(11);
  337. MAP_BUTTON(12);
  338. MAP_BUTTON(13);
  339. MAP_BUTTON(14);
  340. MAP_BUTTON(15);
  341. MAP_BUTTON(16);
  342. MAP_BUTTON(17);
  343. MAP_BUTTON(18);
  344. MAP_BUTTON(19);
  345. MAP_BUTTON(20);
  346. MAP_BUTTON(21);
  347. MAP_BUTTON(22);
  348. MAP_BUTTON(23);
  349. MAP_BUTTON(24);
  350. MAP_BUTTON(25);
  351. MAP_BUTTON(26);
  352. MAP_BUTTON(27);
  353. MAP_BUTTON(28);
  354. MAP_BUTTON(29);
  355. default:
  356. break;
  357. }
  358. #undef MAP_BUTTON
  359. #undef MAP_KEY
  360. return false;
  361. }
  362. static bool code_to_str(int code, struct dstr *str)
  363. {
  364. #define MAP_GLYPH(c, g) \
  365. case c: \
  366. dstr_from_wcs(str, (wchar_t[]){g, 0}); \
  367. return true
  368. #define MAP_STR(c, s) \
  369. case c: \
  370. dstr_copy(str, s); \
  371. return true
  372. switch (code) {
  373. MAP_GLYPH(kVK_Return, 0x21A9);
  374. MAP_GLYPH(kVK_Escape, 0x238B);
  375. MAP_GLYPH(kVK_Delete, 0x232B);
  376. MAP_GLYPH(kVK_Tab, 0x21e5);
  377. MAP_GLYPH(kVK_CapsLock, 0x21EA);
  378. MAP_GLYPH(kVK_ANSI_KeypadClear, 0x2327);
  379. MAP_GLYPH(kVK_ANSI_KeypadEnter, 0x2305);
  380. MAP_GLYPH(kVK_Help, 0x003F);
  381. MAP_GLYPH(kVK_Home, 0x2196);
  382. MAP_GLYPH(kVK_PageUp, 0x21de);
  383. MAP_GLYPH(kVK_ForwardDelete, 0x2326);
  384. MAP_GLYPH(kVK_End, 0x2198);
  385. MAP_GLYPH(kVK_PageDown, 0x21df);
  386. MAP_GLYPH(kVK_RightArrow, 0x2192);
  387. MAP_GLYPH(kVK_LeftArrow, 0x2190);
  388. MAP_GLYPH(kVK_DownArrow, 0x2193);
  389. MAP_GLYPH(kVK_UpArrow, 0x2191);
  390. MAP_STR(kVK_F1, "F1");
  391. MAP_STR(kVK_F2, "F2");
  392. MAP_STR(kVK_F3, "F3");
  393. MAP_STR(kVK_F4, "F4");
  394. MAP_STR(kVK_F5, "F5");
  395. MAP_STR(kVK_F6, "F6");
  396. MAP_STR(kVK_F7, "F7");
  397. MAP_STR(kVK_F8, "F8");
  398. MAP_STR(kVK_F9, "F9");
  399. MAP_STR(kVK_F10, "F10");
  400. MAP_STR(kVK_F11, "F11");
  401. MAP_STR(kVK_F12, "F12");
  402. MAP_STR(kVK_F13, "F13");
  403. MAP_STR(kVK_F14, "F14");
  404. MAP_STR(kVK_F15, "F15");
  405. MAP_STR(kVK_F16, "F16");
  406. MAP_STR(kVK_F17, "F17");
  407. MAP_STR(kVK_F18, "F18");
  408. MAP_STR(kVK_F19, "F19");
  409. MAP_STR(kVK_F20, "F20");
  410. MAP_GLYPH(kVK_Control, kControlUnicode);
  411. MAP_GLYPH(kVK_Shift, kShiftUnicode);
  412. MAP_GLYPH(kVK_Option, kOptionUnicode);
  413. MAP_GLYPH(kVK_Command, kCommandUnicode);
  414. MAP_GLYPH(kVK_RightControl, kControlUnicode);
  415. MAP_GLYPH(kVK_RightShift, kShiftUnicode);
  416. MAP_GLYPH(kVK_RightOption, kOptionUnicode);
  417. }
  418. #undef MAP_STR
  419. #undef MAP_GLYPH
  420. return false;
  421. }
  422. void obs_key_to_str(obs_key_t key, struct dstr *str)
  423. {
  424. if (localized_key_to_str(key, str))
  425. return;
  426. int code = obs_key_to_virtual_key(key);
  427. if (code_to_str(code, str))
  428. return;
  429. if (code == INVALID_KEY) {
  430. blog(LOG_ERROR,
  431. "hotkey-cocoa: Got invalid key while "
  432. "translating key '%d' (%s)",
  433. key, obs_key_to_name(key));
  434. goto err;
  435. }
  436. struct obs_hotkeys_platform *plat = NULL;
  437. if (obs) {
  438. pthread_mutex_lock(&obs->hotkeys.mutex);
  439. plat = obs->hotkeys.platform_context;
  440. hotkeys_retain(plat);
  441. pthread_mutex_unlock(&obs->hotkeys.mutex);
  442. }
  443. if (!plat) {
  444. blog(LOG_ERROR,
  445. "hotkey-cocoa: Could not get hotkey platform "
  446. "while translating key '%d' (%s)",
  447. key, obs_key_to_name(key));
  448. goto err;
  449. }
  450. const UniCharCount max_length = 16;
  451. UInt32 dead_key_state = 0;
  452. UniChar buffer[max_length];
  453. UniCharCount len = 0;
  454. OSStatus err =
  455. UCKeyTranslate(plat->layout, code, kUCKeyActionDown,
  456. 0x104, //caps lock for upper case letters
  457. LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit,
  458. &dead_key_state, max_length, &len, buffer);
  459. if (err == noErr && len <= 0 && dead_key_state) {
  460. err = UCKeyTranslate(plat->layout, kVK_Space, kUCKeyActionDown,
  461. 0x104, LMGetKbdType(),
  462. kUCKeyTranslateNoDeadKeysBit,
  463. &dead_key_state, max_length, &len, buffer);
  464. }
  465. hotkeys_release(plat);
  466. if (err != noErr) {
  467. blog(LOG_ERROR,
  468. "hotkey-cocoa: Error while translating key '%d'"
  469. " (0x%x, %s) to string: %d",
  470. key, code, obs_key_to_name(key), err);
  471. goto err;
  472. }
  473. if (len == 0) {
  474. blog(LOG_ERROR,
  475. "hotkey-cocoa: Got 0 length string while "
  476. "translating '%d' (0x%x, %s) to string",
  477. key, code, obs_key_to_name(key));
  478. goto err;
  479. }
  480. CFStringRef string = CFStringCreateWithCharactersNoCopy(
  481. NULL, buffer, len, kCFAllocatorNull);
  482. if (!string) {
  483. blog(LOG_ERROR,
  484. "hotkey-cocoa: Could not create CFStringRef "
  485. "while translating '%d' (0x%x, %s) to string",
  486. key, code, obs_key_to_name(key));
  487. goto err;
  488. }
  489. if (!dstr_from_cfstring(str, string)) {
  490. blog(LOG_ERROR,
  491. "hotkey-cocoa: Could not translate CFStringRef "
  492. "to CString while translating '%d' (0x%x, %s)",
  493. key, code, obs_key_to_name(key));
  494. goto release;
  495. }
  496. CFRelease(string);
  497. return;
  498. release:
  499. CFRelease(string);
  500. err:
  501. dstr_copy(str, obs_key_to_name(key));
  502. }
  503. #define OBS_COCOA_MODIFIER_SIZE 7
  504. static void unichar_to_utf8(const UniChar *c, char *buff)
  505. {
  506. CFStringRef string = CFStringCreateWithCharactersNoCopy(
  507. NULL, c, 2, kCFAllocatorNull);
  508. if (!string) {
  509. blog(LOG_ERROR, "hotkey-cocoa: Could not create CFStringRef "
  510. "while populating modifier strings");
  511. return;
  512. }
  513. if (!CFStringGetCString(string, buff, OBS_COCOA_MODIFIER_SIZE,
  514. kCFStringEncodingUTF8))
  515. blog(LOG_ERROR,
  516. "hotkey-cocoa: Error while populating "
  517. " modifier string with glyph %d (0x%x)",
  518. c[0], c[0]);
  519. CFRelease(string);
  520. }
  521. static char ctrl_str[OBS_COCOA_MODIFIER_SIZE];
  522. static char opt_str[OBS_COCOA_MODIFIER_SIZE];
  523. static char shift_str[OBS_COCOA_MODIFIER_SIZE];
  524. static char cmd_str[OBS_COCOA_MODIFIER_SIZE];
  525. static void init_utf_8_strings(void)
  526. {
  527. const UniChar ctrl_uni[] = {kControlUnicode, 0};
  528. const UniChar opt_uni[] = {kOptionUnicode, 0};
  529. const UniChar shift_uni[] = {kShiftUnicode, 0};
  530. const UniChar cmd_uni[] = {kCommandUnicode, 0};
  531. unichar_to_utf8(ctrl_uni, ctrl_str);
  532. unichar_to_utf8(opt_uni, opt_str);
  533. unichar_to_utf8(shift_uni, shift_str);
  534. unichar_to_utf8(cmd_uni, cmd_str);
  535. }
  536. static pthread_once_t strings_token = PTHREAD_ONCE_INIT;
  537. void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str)
  538. {
  539. struct dstr key_str = {0};
  540. if (key.key != OBS_KEY_NONE)
  541. obs_key_to_str(key.key, &key_str);
  542. int res = pthread_once(&strings_token, init_utf_8_strings);
  543. if (res) {
  544. blog(LOG_ERROR,
  545. "hotkeys-cocoa: Error while translating "
  546. "modifiers %d (0x%x)",
  547. res, res);
  548. dstr_move(str, &key_str);
  549. return;
  550. }
  551. #define CHECK_MODIFIER(mod, str) ((key.modifiers & mod) ? str : "")
  552. dstr_printf(str, "%s%s%s%s%s",
  553. CHECK_MODIFIER(INTERACT_CONTROL_KEY, ctrl_str),
  554. CHECK_MODIFIER(INTERACT_ALT_KEY, opt_str),
  555. CHECK_MODIFIER(INTERACT_SHIFT_KEY, shift_str),
  556. CHECK_MODIFIER(INTERACT_COMMAND_KEY, cmd_str),
  557. key_str.len ? key_str.array : "");
  558. #undef CHECK_MODIFIER
  559. dstr_free(&key_str);
  560. }
  561. static bool log_layout_name(TISInputSourceRef tis)
  562. {
  563. struct dstr layout_name = {0};
  564. CFStringRef sid = (CFStringRef)TISGetInputSourceProperty(
  565. tis, kTISPropertyInputSourceID);
  566. if (!sid) {
  567. blog(LOG_ERROR, "hotkeys-cocoa: Failed getting InputSourceID");
  568. return false;
  569. }
  570. if (!dstr_from_cfstring(&layout_name, sid)) {
  571. blog(LOG_ERROR, "hotkeys-cocoa: Could not convert InputSourceID"
  572. " to CString");
  573. goto fail;
  574. }
  575. blog(LOG_INFO, "hotkeys-cocoa: Using layout '%s'", layout_name.array);
  576. dstr_free(&layout_name);
  577. return true;
  578. fail:
  579. dstr_free(&layout_name);
  580. return false;
  581. }
  582. static void handle_monitor_event(obs_hotkeys_platform_t *plat, NSEvent *event)
  583. {
  584. if (event.type == NSEventTypeFlagsChanged) {
  585. NSEventModifierFlags flags = event.modifierFlags;
  586. plat->is_key_down[OBS_KEY_CAPSLOCK] =
  587. !!(flags & NSEventModifierFlagCapsLock);
  588. plat->is_key_down[OBS_KEY_SHIFT] =
  589. !!(flags & NSEventModifierFlagShift);
  590. plat->is_key_down[OBS_KEY_ALT] =
  591. !!(flags & NSEventModifierFlagOption);
  592. plat->is_key_down[OBS_KEY_META] =
  593. !!(flags & NSEventModifierFlagCommand);
  594. plat->is_key_down[OBS_KEY_CONTROL] =
  595. !!(flags & NSEventModifierFlagControl);
  596. } else if (event.type == NSEventTypeKeyDown ||
  597. event.type == NSEventTypeKeyUp) {
  598. plat->is_key_down[obs_key_from_virtual_key(event.keyCode)] =
  599. (event.type == NSEventTypeKeyDown);
  600. }
  601. }
  602. static bool init_hotkeys_platform(obs_hotkeys_platform_t **plat_)
  603. {
  604. if (!plat_)
  605. return false;
  606. *plat_ = bzalloc(sizeof(obs_hotkeys_platform_t));
  607. obs_hotkeys_platform_t *plat = *plat_;
  608. if (!plat) {
  609. *plat_ = NULL;
  610. return false;
  611. }
  612. void (^handler)(NSEvent *) = ^(NSEvent *event) {
  613. handle_monitor_event(plat, event);
  614. };
  615. plat->monitor = (__bridge CFTypeRef)[NSEvent
  616. addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown |
  617. NSEventMaskKeyUp |
  618. NSEventMaskFlagsChanged
  619. handler:handler];
  620. plat->tis = TISCopyCurrentKeyboardLayoutInputSource();
  621. plat->layout_data = (CFDataRef)TISGetInputSourceProperty(
  622. plat->tis, kTISPropertyUnicodeKeyLayoutData);
  623. if (!plat->layout_data) {
  624. blog(LOG_ERROR, "hotkeys-cocoa: Failed getting LayoutData");
  625. goto fail;
  626. }
  627. CFRetain(plat->layout_data);
  628. plat->layout = (UCKeyboardLayout *)CFDataGetBytePtr(plat->layout_data);
  629. return true;
  630. fail:
  631. hotkeys_release(plat);
  632. *plat_ = NULL;
  633. return false;
  634. }
  635. static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat)
  636. {
  637. if (!plat)
  638. return;
  639. if (plat->monitor) {
  640. CFRelease(plat->monitor);
  641. plat->monitor = NULL;
  642. }
  643. if (plat->tis) {
  644. CFRelease(plat->tis);
  645. plat->tis = NULL;
  646. }
  647. if (plat->layout_data) {
  648. CFRelease(plat->layout_data);
  649. plat->layout_data = NULL;
  650. }
  651. bfree(plat);
  652. }
  653. static void input_method_changed(CFNotificationCenterRef nc, void *observer,
  654. CFStringRef name, const void *object,
  655. CFDictionaryRef user_info)
  656. {
  657. UNUSED_PARAMETER(nc);
  658. UNUSED_PARAMETER(name);
  659. UNUSED_PARAMETER(object);
  660. UNUSED_PARAMETER(user_info);
  661. struct obs_core_hotkeys *hotkeys = observer;
  662. obs_hotkeys_platform_t *new_plat;
  663. if (init_hotkeys_platform(&new_plat)) {
  664. obs_hotkeys_platform_t *plat;
  665. pthread_mutex_lock(&hotkeys->mutex);
  666. plat = hotkeys->platform_context;
  667. if (new_plat && plat &&
  668. new_plat->layout_data == plat->layout_data) {
  669. pthread_mutex_unlock(&hotkeys->mutex);
  670. hotkeys_release(new_plat);
  671. return;
  672. }
  673. hotkeys->platform_context = new_plat;
  674. if (new_plat)
  675. log_layout_name(new_plat->tis);
  676. pthread_mutex_unlock(&hotkeys->mutex);
  677. calldata_t params = {0};
  678. signal_handler_signal(hotkeys->signals, "hotkey_layout_change",
  679. &params);
  680. if (plat)
  681. hotkeys_release(plat);
  682. }
  683. }
  684. bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys)
  685. {
  686. CFNotificationCenterAddObserver(
  687. CFNotificationCenterGetDistributedCenter(), hotkeys,
  688. input_method_changed,
  689. kTISNotifySelectedKeyboardInputSourceChanged, NULL,
  690. CFNotificationSuspensionBehaviorDeliverImmediately);
  691. input_method_changed(NULL, hotkeys, NULL, NULL, NULL);
  692. return hotkeys->platform_context != NULL;
  693. }
  694. void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys)
  695. {
  696. CFNotificationCenterRemoveEveryObserver(
  697. CFNotificationCenterGetDistributedCenter(), hotkeys);
  698. hotkeys_release(hotkeys->platform_context);
  699. }
  700. typedef unsigned long NSUInteger;
  701. static bool mouse_button_pressed(obs_key_t key, bool *pressed)
  702. {
  703. int button = 0;
  704. switch (key) {
  705. #define MAP_BUTTON(n) \
  706. case OBS_KEY_MOUSE##n: \
  707. button = n - 1; \
  708. break
  709. MAP_BUTTON(1);
  710. MAP_BUTTON(2);
  711. MAP_BUTTON(3);
  712. MAP_BUTTON(4);
  713. MAP_BUTTON(5);
  714. MAP_BUTTON(6);
  715. MAP_BUTTON(7);
  716. MAP_BUTTON(8);
  717. MAP_BUTTON(9);
  718. MAP_BUTTON(10);
  719. MAP_BUTTON(11);
  720. MAP_BUTTON(12);
  721. MAP_BUTTON(13);
  722. MAP_BUTTON(14);
  723. MAP_BUTTON(15);
  724. MAP_BUTTON(16);
  725. MAP_BUTTON(17);
  726. MAP_BUTTON(18);
  727. MAP_BUTTON(19);
  728. MAP_BUTTON(20);
  729. MAP_BUTTON(21);
  730. MAP_BUTTON(22);
  731. MAP_BUTTON(23);
  732. MAP_BUTTON(24);
  733. MAP_BUTTON(25);
  734. MAP_BUTTON(26);
  735. MAP_BUTTON(27);
  736. MAP_BUTTON(28);
  737. MAP_BUTTON(29);
  738. break;
  739. #undef MAP_BUTTON
  740. default:
  741. return false;
  742. }
  743. NSUInteger buttons = [NSEvent pressedMouseButtons];
  744. *pressed = (buttons & (1 << button)) != 0;
  745. return true;
  746. }
  747. bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *plat,
  748. obs_key_t key)
  749. {
  750. bool mouse_pressed = false;
  751. if (mouse_button_pressed(key, &mouse_pressed))
  752. return mouse_pressed;
  753. if (!plat)
  754. return false;
  755. if (key >= OBS_KEY_LAST_VALUE)
  756. return false;
  757. return plat->is_key_down[key];
  758. }
  759. void *obs_graphics_thread_autorelease(void *param)
  760. {
  761. @autoreleasepool {
  762. return obs_graphics_thread(param);
  763. }
  764. }
  765. bool obs_graphics_thread_loop_autorelease(struct obs_graphics_context *context)
  766. {
  767. @autoreleasepool {
  768. return obs_graphics_thread_loop(context);
  769. }
  770. }