tauri-edge.h 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2017 Serge Zaitsev
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. * SOFTWARE.
  23. */
  24. #ifndef WEBVIEW_H
  25. #define WEBVIEW_H
  26. #ifndef WEBVIEW_API
  27. #define WEBVIEW_API extern
  28. #endif
  29. #include <stdint.h>
  30. #ifdef __cplusplus
  31. extern "C"
  32. {
  33. #endif
  34. typedef void *webview_t;
  35. typedef void (*webview_external_invoke_cb_t)(webview_t w, const char *arg);
  36. // Create a new webview instance
  37. WEBVIEW_API webview_t webview_create(webview_external_invoke_cb_t invoke_cb, int width, int height, int resizable, int debug);
  38. // Destroy a webview
  39. WEBVIEW_API void webview_destroy(webview_t w);
  40. // Run the main loop
  41. WEBVIEW_API void webview_run(webview_t w);
  42. // Stop the main loop
  43. WEBVIEW_API void webview_terminate(webview_t w);
  44. // Post a function to be executed on the main thread
  45. WEBVIEW_API void webview_dispatch(
  46. webview_t w, void (*fn)(webview_t w, void *arg), void *arg);
  47. WEBVIEW_API void *webview_get_window(webview_t w);
  48. WEBVIEW_API void webview_set_title(webview_t w, const char *title);
  49. WEBVIEW_API void webview_navigate(webview_t w, const char *url);
  50. WEBVIEW_API void webview_init(webview_t w, const char *js);
  51. WEBVIEW_API int webview_eval(webview_t w, const char *js);
  52. WEBVIEW_API int webview_loop(webview_t w, int blocking);
  53. WEBVIEW_API void *webview_get_userdata(webview_t w);
  54. WEBVIEW_API void webview_set_userdata(webview_t w, void *user_data);
  55. // Enable or disable window fullscreen
  56. WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen);
  57. // Set rgba color of the window's title bar
  58. WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
  59. // Inject css into webview's page
  60. WEBVIEW_API int webview_inject_css(webview_t w, const char *css);
  61. WEBVIEW_API void webview_dialog(webview_t w,
  62. enum webview_dialog_type dlgtype, int flags,
  63. const char *title, const char *arg,
  64. char *result, size_t resultsz);
  65. #ifdef __cplusplus
  66. }
  67. #endif
  68. enum webview_dialog_type
  69. {
  70. WEBVIEW_DIALOG_TYPE_OPEN = 0,
  71. WEBVIEW_DIALOG_TYPE_SAVE = 1,
  72. WEBVIEW_DIALOG_TYPE_ALERT = 2
  73. };
  74. #define WEBVIEW_DIALOG_FLAG_FILE (0 << 0)
  75. #define WEBVIEW_DIALOG_FLAG_DIRECTORY (1 << 0)
  76. #define WEBVIEW_DIALOG_FLAG_INFO (1 << 1)
  77. #define WEBVIEW_DIALOG_FLAG_WARNING (2 << 1)
  78. #define WEBVIEW_DIALOG_FLAG_ERROR (3 << 1)
  79. #define WEBVIEW_DIALOG_FLAG_ALERT_MASK (3 << 1)
  80. #ifndef WEBVIEW_HEADER
  81. #include <atomic>
  82. #include <cstring>
  83. #include <functional>
  84. #include <future>
  85. #include <map>
  86. #include <string>
  87. #include <vector>
  88. //
  89. // ====================================================================
  90. //
  91. // This implementation uses Win32 API to create a native window. It can
  92. // use either MSHTML or EdgeHTML backend as a browser engine.
  93. //
  94. // ====================================================================
  95. //
  96. #define WIN32_LEAN_AND_MEAN
  97. #include <objbase.h>
  98. #include <windows.h>
  99. #include <wingdi.h>
  100. #include <winrt/Windows.Foundation.h>
  101. #include <winrt/Windows.Web.UI.Interop.h>
  102. #pragma comment(lib, "windowsapp")
  103. #pragma comment(lib, "user32.lib")
  104. #pragma comment(lib, "gdi32")
  105. namespace webview
  106. {
  107. using dispatch_fn_t = std::function<void()>;
  108. using msg_cb_t = std::function<void(const char *msg)>;
  109. inline std::string url_encode(std::string s)
  110. {
  111. std::string encoded;
  112. for (unsigned int i = 0; i < s.length(); i++)
  113. {
  114. auto c = s[i];
  115. if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
  116. {
  117. encoded = encoded + c;
  118. }
  119. else
  120. {
  121. char hex[4];
  122. snprintf(hex, sizeof(hex), "%%%02x", c);
  123. encoded = encoded + hex;
  124. }
  125. }
  126. return encoded;
  127. }
  128. inline std::string url_decode(std::string s)
  129. {
  130. std::string decoded;
  131. for (unsigned int i = 0; i < s.length(); i++)
  132. {
  133. if (s[i] == '%')
  134. {
  135. int n;
  136. sscanf(s.substr(i + 1, 2).c_str(), "%x", &n);
  137. decoded = decoded + static_cast<char>(n);
  138. i = i + 2;
  139. }
  140. else if (s[i] == '+')
  141. {
  142. decoded = decoded + ' ';
  143. }
  144. else
  145. {
  146. decoded = decoded + s[i];
  147. }
  148. }
  149. return decoded;
  150. }
  151. inline std::string html_from_uri(std::string s)
  152. {
  153. if (s.substr(0, 15) == "data:text/html,")
  154. {
  155. return url_decode(s.substr(15));
  156. }
  157. return "";
  158. }
  159. inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
  160. const char **value, size_t *valuesz)
  161. {
  162. enum
  163. {
  164. JSON_STATE_VALUE,
  165. JSON_STATE_LITERAL,
  166. JSON_STATE_STRING,
  167. JSON_STATE_ESCAPE,
  168. JSON_STATE_UTF8
  169. } state = JSON_STATE_VALUE;
  170. const char *k = NULL;
  171. int index = 1;
  172. int depth = 0;
  173. int utf8_bytes = 0;
  174. if (key == NULL)
  175. {
  176. index = keysz;
  177. keysz = 0;
  178. }
  179. *value = NULL;
  180. *valuesz = 0;
  181. for (; sz > 0; s++, sz--)
  182. {
  183. enum
  184. {
  185. JSON_ACTION_NONE,
  186. JSON_ACTION_START,
  187. JSON_ACTION_END,
  188. JSON_ACTION_START_STRUCT,
  189. JSON_ACTION_END_STRUCT
  190. } action = JSON_ACTION_NONE;
  191. unsigned char c = *s;
  192. switch (state)
  193. {
  194. case JSON_STATE_VALUE:
  195. if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':')
  196. {
  197. continue;
  198. }
  199. else if (c == '"')
  200. {
  201. action = JSON_ACTION_START;
  202. state = JSON_STATE_STRING;
  203. }
  204. else if (c == '{' || c == '[')
  205. {
  206. action = JSON_ACTION_START_STRUCT;
  207. }
  208. else if (c == '}' || c == ']')
  209. {
  210. action = JSON_ACTION_END_STRUCT;
  211. }
  212. else if (c == 't' || c == 'f' || c == 'n' || c == '-' || (c >= '0' && c <= '9'))
  213. {
  214. action = JSON_ACTION_START;
  215. state = JSON_STATE_LITERAL;
  216. }
  217. else
  218. {
  219. return -1;
  220. }
  221. break;
  222. case JSON_STATE_LITERAL:
  223. if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ']' || c == '}' || c == ':')
  224. {
  225. state = JSON_STATE_VALUE;
  226. s--;
  227. sz++;
  228. action = JSON_ACTION_END;
  229. }
  230. else if (c < 32 || c > 126)
  231. {
  232. return -1;
  233. } // fallthrough
  234. case JSON_STATE_STRING:
  235. if (c < 32 || (c > 126 && c < 192))
  236. {
  237. return -1;
  238. }
  239. else if (c == '"')
  240. {
  241. action = JSON_ACTION_END;
  242. state = JSON_STATE_VALUE;
  243. }
  244. else if (c == '\\')
  245. {
  246. state = JSON_STATE_ESCAPE;
  247. }
  248. else if (c >= 192 && c < 224)
  249. {
  250. utf8_bytes = 1;
  251. state = JSON_STATE_UTF8;
  252. }
  253. else if (c >= 224 && c < 240)
  254. {
  255. utf8_bytes = 2;
  256. state = JSON_STATE_UTF8;
  257. }
  258. else if (c >= 240 && c < 247)
  259. {
  260. utf8_bytes = 3;
  261. state = JSON_STATE_UTF8;
  262. }
  263. else if (c >= 128 && c < 192)
  264. {
  265. return -1;
  266. }
  267. break;
  268. case JSON_STATE_ESCAPE:
  269. if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' || c == 'r' || c == 't' || c == 'u')
  270. {
  271. state = JSON_STATE_STRING;
  272. }
  273. else
  274. {
  275. return -1;
  276. }
  277. break;
  278. case JSON_STATE_UTF8:
  279. if (c < 128 || c > 191)
  280. {
  281. return -1;
  282. }
  283. utf8_bytes--;
  284. if (utf8_bytes == 0)
  285. {
  286. state = JSON_STATE_STRING;
  287. }
  288. break;
  289. default:
  290. return -1;
  291. }
  292. if (action == JSON_ACTION_END_STRUCT)
  293. {
  294. depth--;
  295. }
  296. if (depth == 1)
  297. {
  298. if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT)
  299. {
  300. if (index == 0)
  301. {
  302. *value = s;
  303. }
  304. else if (keysz > 0 && index == 1)
  305. {
  306. k = s;
  307. }
  308. else
  309. {
  310. index--;
  311. }
  312. }
  313. else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT)
  314. {
  315. if (*value != NULL && index == 0)
  316. {
  317. *valuesz = (size_t)(s + 1 - *value);
  318. return 0;
  319. }
  320. else if (keysz > 0 && k != NULL)
  321. {
  322. if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0)
  323. {
  324. index = 0;
  325. }
  326. else
  327. {
  328. index = 2;
  329. }
  330. k = NULL;
  331. }
  332. }
  333. }
  334. if (action == JSON_ACTION_START_STRUCT)
  335. {
  336. depth++;
  337. }
  338. }
  339. return -1;
  340. }
  341. inline std::string json_escape(std::string s)
  342. {
  343. std::string r = "\"";
  344. r.reserve(s.size() + 4);
  345. static const char *h = "0123456789abcdef";
  346. const unsigned char *d = reinterpret_cast<const unsigned char *>(s.data());
  347. for (size_t i = 0; i < s.size(); ++i)
  348. {
  349. switch (const auto c = d[i])
  350. {
  351. case '\b':
  352. r += "\\b";
  353. break;
  354. case '\f':
  355. r += "\\f";
  356. break;
  357. case '\n':
  358. r += "\\n";
  359. break;
  360. case '\r':
  361. r += "\\r";
  362. break;
  363. case '\t':
  364. r += "\\t";
  365. break;
  366. case '\\':
  367. r += "\\\\";
  368. break;
  369. case '\"':
  370. r += "\\\"";
  371. break;
  372. default:
  373. if ((c < 32) || (c == 127))
  374. {
  375. r += "\\u00";
  376. r += h[(c & 0xf0) >> 4];
  377. r += h[c & 0x0f];
  378. continue;
  379. }
  380. r += c; // Assume valid UTF-8.
  381. break;
  382. }
  383. }
  384. r += '"';
  385. return r;
  386. }
  387. inline int json_unescape(const char *s, size_t n, char *out)
  388. {
  389. int r = 0;
  390. if (*s++ != '"')
  391. {
  392. return -1;
  393. }
  394. while (n > 2)
  395. {
  396. char c = *s;
  397. if (c == '\\')
  398. {
  399. s++;
  400. n--;
  401. switch (*s)
  402. {
  403. case 'b':
  404. c = '\b';
  405. break;
  406. case 'f':
  407. c = '\f';
  408. break;
  409. case 'n':
  410. c = '\n';
  411. break;
  412. case 'r':
  413. c = '\r';
  414. break;
  415. case 't':
  416. c = '\t';
  417. break;
  418. case '\\':
  419. c = '\\';
  420. break;
  421. case '/':
  422. c = '/';
  423. break;
  424. case '\"':
  425. c = '\"';
  426. break;
  427. default: // TODO: support unicode decoding
  428. return -1;
  429. }
  430. }
  431. if (out != NULL)
  432. {
  433. *out++ = c;
  434. }
  435. s++;
  436. n--;
  437. r++;
  438. }
  439. if (*s != '"')
  440. {
  441. return -1;
  442. }
  443. if (out != NULL)
  444. {
  445. *out = '\0';
  446. }
  447. return r;
  448. }
  449. inline std::string json_parse(std::string s, std::string key, int index)
  450. {
  451. const char *value;
  452. size_t value_sz;
  453. if (key == "")
  454. {
  455. json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
  456. }
  457. else
  458. {
  459. json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz);
  460. }
  461. if (value != nullptr)
  462. {
  463. if (value[0] != '"')
  464. {
  465. return std::string(value, value_sz);
  466. }
  467. int n = json_unescape(value, value_sz, nullptr);
  468. if (n > 0)
  469. {
  470. char *decoded = new char[n];
  471. json_unescape(value, value_sz, decoded);
  472. auto result = std::string(decoded, n);
  473. delete[] decoded;
  474. return result;
  475. }
  476. }
  477. return "";
  478. }
  479. LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
  480. class browser_window
  481. {
  482. public:
  483. browser_window(msg_cb_t cb, const char *title, int width, int height, bool resizable)
  484. : m_cb(cb)
  485. {
  486. HINSTANCE hInstance = GetModuleHandle(nullptr);
  487. WNDCLASSEX wc;
  488. ZeroMemory(&wc, sizeof(WNDCLASSEX));
  489. wc.cbSize = sizeof(WNDCLASSEX);
  490. wc.hInstance = hInstance;
  491. wc.lpfnWndProc = WebviewWndProc;
  492. wc.lpszClassName = "webview";
  493. RegisterClassEx(&wc);
  494. DWORD style = WS_OVERLAPPEDWINDOW;
  495. if (!resizable)
  496. {
  497. style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
  498. }
  499. RECT clientRect;
  500. RECT rect;
  501. rect.left = 0;
  502. rect.top = 0;
  503. rect.right = width;
  504. rect.bottom = height;
  505. AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
  506. GetClientRect(GetDesktopWindow(), &clientRect);
  507. int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2);
  508. int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2);
  509. rect.right = rect.right - rect.left + left;
  510. rect.left = left;
  511. rect.bottom = rect.bottom - rect.top + top;
  512. rect.top = top;
  513. m_window = CreateWindowEx(0, "webview", title, style, rect.left, rect.top,
  514. rect.right - rect.left, rect.bottom - rect.top,
  515. HWND_DESKTOP, NULL, hInstance, (void *)this);
  516. SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
  517. ShowWindow(m_window, SW_SHOW);
  518. UpdateWindow(m_window);
  519. SetFocus(m_window);
  520. }
  521. void run()
  522. {
  523. while (this->loop(true) == 0)
  524. {
  525. }
  526. }
  527. int loop(int blocking)
  528. {
  529. MSG msg;
  530. if (blocking)
  531. {
  532. if (GetMessage(&msg, nullptr, 0, 0) < 0)
  533. return 0;
  534. }
  535. else
  536. {
  537. if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) == 0)
  538. return 0;
  539. }
  540. if (msg.hwnd)
  541. {
  542. TranslateMessage(&msg);
  543. DispatchMessage(&msg);
  544. return 0;
  545. }
  546. if (msg.message == WM_APP)
  547. {
  548. auto f = (dispatch_fn_t *)(msg.lParam);
  549. (*f)();
  550. delete f;
  551. }
  552. else if (msg.message == WM_QUIT)
  553. {
  554. return -1;
  555. }
  556. return 0;
  557. }
  558. void terminate() { PostQuitMessage(0); }
  559. void dispatch(dispatch_fn_t f)
  560. {
  561. PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
  562. }
  563. void set_title(const char *title) { SetWindowText(m_window, title); }
  564. void set_size(int width, int height)
  565. {
  566. RECT r;
  567. r.left = 50;
  568. r.top = 50;
  569. r.right = width;
  570. r.bottom = height;
  571. AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
  572. SetWindowPos(m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
  573. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  574. }
  575. void set_fullscreen(bool fullscreen)
  576. {
  577. if (this->is_fullscreen == fullscreen)
  578. {
  579. return;
  580. }
  581. if (!this->is_fullscreen)
  582. {
  583. this->saved_style = GetWindowLong(this->m_window, GWL_STYLE);
  584. this->saved_ex_style = GetWindowLong(this->m_window, GWL_EXSTYLE);
  585. GetWindowRect(this->m_window, &this->saved_rect);
  586. }
  587. this->is_fullscreen = !!fullscreen;
  588. if (fullscreen)
  589. {
  590. MONITORINFO monitor_info;
  591. SetWindowLong(this->m_window, GWL_STYLE,
  592. this->saved_style & ~(WS_CAPTION | WS_THICKFRAME));
  593. SetWindowLong(this->m_window, GWL_EXSTYLE,
  594. this->saved_ex_style &
  595. ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
  596. WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
  597. monitor_info.cbSize = sizeof(monitor_info);
  598. GetMonitorInfo(MonitorFromWindow(this->m_window, MONITOR_DEFAULTTONEAREST),
  599. &monitor_info);
  600. RECT r;
  601. r.left = monitor_info.rcMonitor.left;
  602. r.top = monitor_info.rcMonitor.top;
  603. r.right = monitor_info.rcMonitor.right;
  604. r.bottom = monitor_info.rcMonitor.bottom;
  605. SetWindowPos(this->m_window, NULL, r.left, r.top, r.right - r.left,
  606. r.bottom - r.top,
  607. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  608. }
  609. else
  610. {
  611. SetWindowLong(this->m_window, GWL_STYLE, this->saved_style);
  612. SetWindowLong(this->m_window, GWL_EXSTYLE, this->saved_ex_style);
  613. SetWindowPos(this->m_window, NULL, this->saved_rect.left,
  614. this->saved_rect.top,
  615. this->saved_rect.right - this->saved_rect.left,
  616. this->saved_rect.bottom - this->saved_rect.top,
  617. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  618. }
  619. }
  620. void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
  621. {
  622. HBRUSH brush = CreateSolidBrush(RGB(r, g, b));
  623. SetClassLongPtr(this->m_window, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
  624. }
  625. // protected:
  626. virtual void resize() {}
  627. HWND m_window;
  628. DWORD m_main_thread = GetCurrentThreadId();
  629. msg_cb_t m_cb;
  630. bool is_fullscreen = false;
  631. DWORD saved_style = 0;
  632. DWORD saved_ex_style = 0;
  633. RECT saved_rect;
  634. };
  635. LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
  636. {
  637. auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  638. switch (msg)
  639. {
  640. case WM_SIZE:
  641. w->resize();
  642. break;
  643. case WM_CLOSE:
  644. DestroyWindow(hwnd);
  645. break;
  646. case WM_DESTROY:
  647. w->terminate();
  648. break;
  649. default:
  650. return DefWindowProc(hwnd, msg, wp, lp);
  651. }
  652. return 0;
  653. }
  654. using namespace winrt;
  655. using namespace Windows::Foundation;
  656. using namespace Windows::Web::UI;
  657. using namespace Windows::Web::UI::Interop;
  658. class webview : public browser_window
  659. {
  660. public:
  661. webview(webview_external_invoke_cb_t invoke_cb, const char *title, int width, int height, bool resizable, bool debug)
  662. : browser_window(std::bind(&webview::on_message, this, std::placeholders::_1), title, width, height, resizable), invoke_cb(invoke_cb)
  663. {
  664. init_apartment(winrt::apartment_type::single_threaded);
  665. WebViewControlProcessOptions options;
  666. options.PrivateNetworkClientServerCapability(WebViewControlProcessCapabilityState::Enabled);
  667. m_process = WebViewControlProcess(options);
  668. auto op = m_process.CreateWebViewControlAsync(
  669. reinterpret_cast<int64_t>(m_window), Rect());
  670. if (op.Status() != AsyncStatus::Completed)
  671. {
  672. handle h(CreateEvent(nullptr, false, false, nullptr));
  673. op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
  674. HANDLE hs[] = {h.get()};
  675. DWORD i;
  676. CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE,
  677. INFINITE, 1, hs, &i);
  678. }
  679. m_webview = op.GetResults();
  680. m_webview.Settings().IsScriptNotifyAllowed(true);
  681. m_webview.IsVisible(true);
  682. m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
  683. std::string s = winrt::to_string(args.Value());
  684. m_cb(s.c_str());
  685. });
  686. m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
  687. m_webview.AddInitializeScript(winrt::to_hstring(init_js));
  688. });
  689. init("window.external.invoke = s => window.external.notify(s)");
  690. resize();
  691. }
  692. void navigate(const char *url)
  693. {
  694. std::string html = html_from_uri(url);
  695. if (html != "")
  696. {
  697. m_webview.NavigateToString(winrt::to_hstring(html.c_str()));
  698. }
  699. else
  700. {
  701. Uri uri(winrt::to_hstring(url));
  702. m_webview.Navigate(uri);
  703. }
  704. }
  705. void init(const char *js) { init_js = init_js + "(function(){" + js + "})();"; }
  706. void eval(const char *js)
  707. {
  708. m_webview.InvokeScriptAsync(
  709. L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
  710. }
  711. void *window() { return (void *)m_window; }
  712. void *get_user_data() { return this->user_data; }
  713. void set_user_data(void *user_data) { this->user_data = user_data; }
  714. private:
  715. void on_message(const char *msg)
  716. {
  717. this->invoke_cb(this, msg);
  718. }
  719. void resize()
  720. {
  721. RECT r;
  722. GetClientRect(m_window, &r);
  723. Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
  724. m_webview.Bounds(bounds);
  725. }
  726. WebViewControlProcess m_process;
  727. WebViewControl m_webview = nullptr;
  728. std::string init_js = "";
  729. void *user_data = nullptr;
  730. webview_external_invoke_cb_t invoke_cb;
  731. };
  732. } // namespace webview
  733. static inline char *webview_from_utf16(WCHAR *ws)
  734. {
  735. int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
  736. char *s = (char *)GlobalAlloc(GMEM_FIXED, n);
  737. if (s == NULL)
  738. {
  739. return NULL;
  740. }
  741. WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL);
  742. return s;
  743. }
  744. WEBVIEW_API webview_t webview_create(webview_external_invoke_cb_t invoke_cb, const char *title, int width, int height, int resizable, int debug)
  745. {
  746. return new webview::webview(invoke_cb, title, width, height, resizable, debug);
  747. }
  748. WEBVIEW_API void webview_destroy(webview_t w)
  749. {
  750. delete static_cast<webview::webview *>(w);
  751. }
  752. WEBVIEW_API void webview_run(webview_t w) { static_cast<webview::webview *>(w)->run(); }
  753. WEBVIEW_API void webview_terminate(webview_t w)
  754. {
  755. static_cast<webview::webview *>(w)->terminate();
  756. }
  757. WEBVIEW_API void webview_dispatch(
  758. webview_t w, void (*fn)(webview_t w, void *arg), void *arg)
  759. {
  760. static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
  761. }
  762. WEBVIEW_API void *webview_get_window(webview_t w)
  763. {
  764. return static_cast<webview::webview *>(w)->window();
  765. }
  766. WEBVIEW_API void webview_set_title(webview_t w, const char *title)
  767. {
  768. static_cast<webview::webview *>(w)->set_title(title);
  769. }
  770. WEBVIEW_API void webview_navigate(webview_t w, const char *url)
  771. {
  772. static_cast<webview::webview *>(w)->navigate(url);
  773. }
  774. WEBVIEW_API void webview_init(webview_t w, const char *js)
  775. {
  776. static_cast<webview::webview *>(w)->init(js);
  777. }
  778. WEBVIEW_API int webview_eval(webview_t w, const char *js)
  779. {
  780. static_cast<webview::webview *>(w)->eval(js);
  781. return 0;
  782. }
  783. WEBVIEW_API int webview_loop(webview_t w, int blocking)
  784. {
  785. return static_cast<webview::webview *>(w)->loop(blocking);
  786. }
  787. WEBVIEW_API void *webview_get_userdata(webview_t w)
  788. {
  789. return static_cast<webview::webview *>(w)->get_user_data();
  790. }
  791. WEBVIEW_API void webview_set_userdata(webview_t w, void *user_data)
  792. {
  793. static_cast<webview::webview *>(w)->set_user_data(user_data);
  794. }
  795. WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen)
  796. {
  797. static_cast<webview::webview *>(w)->set_fullscreen(fullscreen);
  798. }
  799. WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
  800. {
  801. static_cast<webview::webview *>(w)->set_color(r, g, b, a);
  802. }
  803. #define CSS_INJECT_FUNCTION \
  804. "(function(e){var " \
  805. "t=document.createElement('style'),d=document.head||document." \
  806. "getElementsByTagName('head')[0];t.setAttribute('type','text/" \
  807. "css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document." \
  808. "createTextNode(e)),d.appendChild(t)})"
  809. static int webview_js_encode(const char *s, char *esc, size_t n)
  810. {
  811. int r = 1; /* At least one byte for trailing zero */
  812. for (; *s; s++)
  813. {
  814. const unsigned char c = *s;
  815. if (c >= 0x20 && c < 0x80 && strchr("<>\\'\"", c) == NULL)
  816. {
  817. if (n > 0)
  818. {
  819. *esc++ = c;
  820. n--;
  821. }
  822. r++;
  823. }
  824. else
  825. {
  826. if (n > 0)
  827. {
  828. snprintf(esc, n, "\\x%02x", (int)c);
  829. esc += 4;
  830. n -= 4;
  831. }
  832. r += 4;
  833. }
  834. }
  835. return r;
  836. }
  837. WEBVIEW_API int webview_inject_css(webview_t w, const char *css)
  838. {
  839. int n = webview_js_encode(css, NULL, 0);
  840. char *esc = (char *)calloc(1, sizeof(CSS_INJECT_FUNCTION) + n + 4);
  841. if (esc == NULL)
  842. {
  843. return -1;
  844. }
  845. char *js = (char *)calloc(1, n);
  846. webview_js_encode(css, js, n);
  847. snprintf(esc, sizeof(CSS_INJECT_FUNCTION) + n + 4, "%s(\"%s\")",
  848. CSS_INJECT_FUNCTION, js);
  849. int r = webview_eval(w, esc);
  850. free(js);
  851. free(esc);
  852. return r;
  853. }
  854. #include <shobjidl.h>
  855. #ifdef __cplusplus
  856. #define iid_ref(x) &(x)
  857. #define iid_unref(x) *(x)
  858. #else
  859. #define iid_ref(x) (x)
  860. #define iid_unref(x) (x)
  861. #endif
  862. /* These are missing parts from MinGW */
  863. #ifndef __IFileDialog_INTERFACE_DEFINED__
  864. #define __IFileDialog_INTERFACE_DEFINED__
  865. enum _FILEOPENDIALOGOPTIONS
  866. {
  867. FOS_OVERWRITEPROMPT = 0x2,
  868. FOS_STRICTFILETYPES = 0x4,
  869. FOS_NOCHANGEDIR = 0x8,
  870. FOS_PICKFOLDERS = 0x20,
  871. FOS_FORCEFILESYSTEM = 0x40,
  872. FOS_ALLNONSTORAGEITEMS = 0x80,
  873. FOS_NOVALIDATE = 0x100,
  874. FOS_ALLOWMULTISELECT = 0x200,
  875. FOS_PATHMUSTEXIST = 0x800,
  876. FOS_FILEMUSTEXIST = 0x1000,
  877. FOS_CREATEPROMPT = 0x2000,
  878. FOS_SHAREAWARE = 0x4000,
  879. FOS_NOREADONLYRETURN = 0x8000,
  880. FOS_NOTESTFILECREATE = 0x10000,
  881. FOS_HIDEMRUPLACES = 0x20000,
  882. FOS_HIDEPINNEDPLACES = 0x40000,
  883. FOS_NODEREFERENCELINKS = 0x100000,
  884. FOS_DONTADDTORECENT = 0x2000000,
  885. FOS_FORCESHOWHIDDEN = 0x10000000,
  886. FOS_DEFAULTNOMINIMODE = 0x20000000,
  887. FOS_FORCEPREVIEWPANEON = 0x40000000
  888. };
  889. typedef DWORD FILEOPENDIALOGOPTIONS;
  890. typedef enum FDAP
  891. {
  892. FDAP_BOTTOM = 0,
  893. FDAP_TOP = 1
  894. } FDAP;
  895. DEFINE_GUID(IID_IFileDialog, 0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07,
  896. 0x5d, 0x13, 0x5f, 0xc8);
  897. typedef struct IFileDialogVtbl
  898. {
  899. BEGIN_INTERFACE
  900. HRESULT(STDMETHODCALLTYPE *QueryInterface)
  901. (IFileDialog *This, REFIID riid, void **ppvObject);
  902. ULONG(STDMETHODCALLTYPE *AddRef)
  903. (IFileDialog *This);
  904. ULONG(STDMETHODCALLTYPE *Release)
  905. (IFileDialog *This);
  906. HRESULT(STDMETHODCALLTYPE *Show)
  907. (IFileDialog *This, HWND hwndOwner);
  908. HRESULT(STDMETHODCALLTYPE *SetFileTypes)
  909. (IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec);
  910. HRESULT(STDMETHODCALLTYPE *SetFileTypeIndex)
  911. (IFileDialog *This, UINT iFileType);
  912. HRESULT(STDMETHODCALLTYPE *GetFileTypeIndex)
  913. (IFileDialog *This, UINT *piFileType);
  914. HRESULT(STDMETHODCALLTYPE *Advise)
  915. (IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie);
  916. HRESULT(STDMETHODCALLTYPE *Unadvise)
  917. (IFileDialog *This, DWORD dwCookie);
  918. HRESULT(STDMETHODCALLTYPE *SetOptions)
  919. (IFileDialog *This, FILEOPENDIALOGOPTIONS fos);
  920. HRESULT(STDMETHODCALLTYPE *GetOptions)
  921. (IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos);
  922. HRESULT(STDMETHODCALLTYPE *SetDefaultFolder)
  923. (IFileDialog *This, IShellItem *psi);
  924. HRESULT(STDMETHODCALLTYPE *SetFolder)
  925. (IFileDialog *This, IShellItem *psi);
  926. HRESULT(STDMETHODCALLTYPE *GetFolder)
  927. (IFileDialog *This, IShellItem **ppsi);
  928. HRESULT(STDMETHODCALLTYPE *GetCurrentSelection)
  929. (IFileDialog *This, IShellItem **ppsi);
  930. HRESULT(STDMETHODCALLTYPE *SetFileName)
  931. (IFileDialog *This, LPCWSTR pszName);
  932. HRESULT(STDMETHODCALLTYPE *GetFileName)
  933. (IFileDialog *This, LPWSTR *pszName);
  934. HRESULT(STDMETHODCALLTYPE *SetTitle)
  935. (IFileDialog *This, LPCWSTR pszTitle);
  936. HRESULT(STDMETHODCALLTYPE *SetOkButtonLabel)
  937. (IFileDialog *This, LPCWSTR pszText);
  938. HRESULT(STDMETHODCALLTYPE *SetFileNameLabel)
  939. (IFileDialog *This, LPCWSTR pszLabel);
  940. HRESULT(STDMETHODCALLTYPE *GetResult)
  941. (IFileDialog *This, IShellItem **ppsi);
  942. HRESULT(STDMETHODCALLTYPE *AddPlace)
  943. (IFileDialog *This, IShellItem *psi, FDAP fdap);
  944. HRESULT(STDMETHODCALLTYPE *SetDefaultExtension)
  945. (IFileDialog *This, LPCWSTR pszDefaultExtension);
  946. HRESULT(STDMETHODCALLTYPE *Close)
  947. (IFileDialog *This, HRESULT hr);
  948. HRESULT(STDMETHODCALLTYPE *SetClientGuid)
  949. (IFileDialog *This, REFGUID guid);
  950. HRESULT(STDMETHODCALLTYPE *ClearClientData)
  951. (IFileDialog *This);
  952. HRESULT(STDMETHODCALLTYPE *SetFilter)
  953. (IFileDialog *This, IShellItemFilter *pFilter);
  954. END_INTERFACE
  955. } IFileDialogVtbl;
  956. interface IFileDialog
  957. {
  958. CONST_VTBL IFileDialogVtbl *lpVtbl;
  959. };
  960. DEFINE_GUID(IID_IFileOpenDialog, 0xd57c7288, 0xd4ad, 0x4768, 0xbe, 0x02, 0x9d,
  961. 0x96, 0x95, 0x32, 0xd9, 0x60);
  962. DEFINE_GUID(IID_IFileSaveDialog, 0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf,
  963. 0x64, 0xb8, 0x3d, 0x78, 0xab);
  964. #endif
  965. WEBVIEW_API void webview_dialog(webview_t w,
  966. enum webview_dialog_type dlgtype, int flags,
  967. const char *title, const char *arg,
  968. char *result, size_t resultsz)
  969. {
  970. HWND hwnd = static_cast<webview::webview *>(w)->m_window;
  971. if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
  972. dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
  973. {
  974. IFileDialog *dlg = NULL;
  975. IShellItem *res = NULL;
  976. WCHAR *ws = NULL;
  977. char *s = NULL;
  978. FILEOPENDIALOGOPTIONS opts = 0, add_opts = 0;
  979. if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN)
  980. {
  981. if (CoCreateInstance(
  982. iid_unref(&CLSID_FileOpenDialog), NULL, CLSCTX_INPROC_SERVER,
  983. iid_unref(&IID_IFileOpenDialog), (void **)&dlg) != S_OK)
  984. {
  985. goto error_dlg;
  986. }
  987. if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY)
  988. {
  989. add_opts |= FOS_PICKFOLDERS;
  990. }
  991. add_opts |= FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE |
  992. FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE |
  993. FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
  994. FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
  995. }
  996. else
  997. {
  998. if (CoCreateInstance(
  999. iid_unref(&CLSID_FileSaveDialog), NULL, CLSCTX_INPROC_SERVER,
  1000. iid_unref(&IID_IFileSaveDialog), (void **)&dlg) != S_OK)
  1001. {
  1002. goto error_dlg;
  1003. }
  1004. add_opts |= FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR |
  1005. FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE |
  1006. FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
  1007. FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
  1008. }
  1009. if (dlg->GetOptions(&opts) != S_OK)
  1010. {
  1011. goto error_dlg;
  1012. }
  1013. opts &= ~FOS_NOREADONLYRETURN;
  1014. opts |= add_opts;
  1015. if (dlg->SetOptions(opts) != S_OK)
  1016. {
  1017. goto error_dlg;
  1018. }
  1019. if (dlg->Show(hwnd) != S_OK)
  1020. {
  1021. goto error_dlg;
  1022. }
  1023. if (dlg->GetResult(&res) != S_OK)
  1024. {
  1025. goto error_dlg;
  1026. }
  1027. if (res->GetDisplayName(SIGDN_FILESYSPATH, &ws) != S_OK)
  1028. {
  1029. goto error_result;
  1030. }
  1031. s = webview_from_utf16(ws);
  1032. CoTaskMemFree(ws);
  1033. if (!s)
  1034. goto error_result;
  1035. strncpy(result, s, resultsz);
  1036. GlobalFree(s);
  1037. result[resultsz - 1] = '\0';
  1038. error_result:
  1039. res->Release();
  1040. error_dlg:
  1041. dlg->Release();
  1042. return;
  1043. }
  1044. else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT)
  1045. {
  1046. #if 0
  1047. /* MinGW often doesn't contain TaskDialog, we'll use MessageBox for now */
  1048. WCHAR *wtitle = webview_to_utf16(title);
  1049. WCHAR *warg = webview_to_utf16(arg);
  1050. TaskDialog(hwnd, NULL, NULL, wtitle, warg, 0, NULL, NULL);
  1051. GlobalFree(warg);
  1052. GlobalFree(wtitle);
  1053. #else
  1054. UINT type = MB_OK;
  1055. switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK)
  1056. {
  1057. case WEBVIEW_DIALOG_FLAG_INFO:
  1058. type |= MB_ICONINFORMATION;
  1059. break;
  1060. case WEBVIEW_DIALOG_FLAG_WARNING:
  1061. type |= MB_ICONWARNING;
  1062. break;
  1063. case WEBVIEW_DIALOG_FLAG_ERROR:
  1064. type |= MB_ICONERROR;
  1065. break;
  1066. }
  1067. MessageBox(hwnd, arg, title, type);
  1068. #endif
  1069. }
  1070. }
  1071. #endif /* WEBVIEW_HEADER */
  1072. #endif /* WEBVIEW_H */