obs-cocoa.m 23 KB

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