1 #include "main.h"
2 #include "notify.h"
3 #include "screen_grab.h"
4 #include "utf8.h"
5 #include "window.h"
6
7 #include "../avatar.h"
8 #include "../commands.h"
9 #include "../debug.h"
10 #include "../file_transfers.h"
11 #include "../filesys.h"
12 #include "../flist.h"
13 #include "../friend.h"
14 #include "../macros.h"
15 #include "../main.h" // Lots of things. :(
16 #include "../self.h"
17 #include "../settings.h"
18 #include "../stb.h"
19 #include "../text.h"
20 #include "../theme.h"
21 #include "../tox.h"
22 #include "../ui.h"
23 #include "../utox.h"
24
25 #include "../av/utox_av.h"
26
27 #include "../native/filesys.h"
28 #include "../native/notify.h"
29 #include "../native/os.h"
30 #include "../native/window.h"
31
32 #include "../ui/draw.h"
33 #include "../ui/dropdown.h"
34 #include "../ui/edit.h"
35 #include "../ui/svg.h"
36
37 #include "../layout/background.h" // TODO do we want to remove this?
38 #include "../layout/friend.h"
39 #include "../layout/group.h"
40 #include "../layout/settings.h" // TODO remove, in for dropdown.lang
41
42 #include <windowsx.h>
43 #include <io.h>
44 #include <libgen.h>
45
46 /**
47 * A null-terminated string that specifies the text for a standard tooltip.
48 * For Windows 2000 and later, szTip can have a maximum of 128 characters,
49 * including the terminating null character.
50 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx
51 */
52 static const uint8_t MAX_TIP_LENGTH = 128 - 1;
53
54 bool flashing = false;
55 bool hidden = false;
56
57 /** Open system file browser dialog */
openfilesend(void)58 void openfilesend(void) {
59 wchar_t dir[UTOX_FILE_NAME_LENGTH];
60 GetCurrentDirectoryW(COUNTOF(dir), dir);
61
62 wchar_t filepath[UTOX_FILE_NAME_LENGTH] = { 0 };
63
64 OPENFILENAMEW ofn = {
65 .lStructSize = sizeof(OPENFILENAMEW),
66 .hwndOwner = main_window.window,
67 .lpstrFile = filepath,
68 .nMaxFile = UTOX_FILE_NAME_LENGTH,
69 .Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST,
70 };
71
72 if (GetOpenFileNameW(&ofn)) {
73 FRIEND *f = flist_get_friend();
74 if (!f) {
75 LOG_ERR("Windows", "Unable to get friend for file send msg.");
76 return;
77 }
78
79 UTOX_MSG_FT *msg = calloc(1, sizeof(UTOX_MSG_FT));
80 if (!msg) {
81 LOG_ERR("Windows", "Unable to calloc for file send msg.");
82 return;
83 }
84
85 char *path = calloc(1, UTOX_FILE_NAME_LENGTH);
86 if (!path) {
87 LOG_ERR("Windows", " Could not allocate memory for path.");
88 return;
89 }
90
91 native_to_utf8str(filepath, path, UTOX_FILE_NAME_LENGTH);
92 msg->file = utox_get_file_simple(path, UTOX_FILE_OPTS_READ);
93 msg->name = (uint8_t *)path;
94
95 postmessage_toxcore(TOX_FILE_SEND_NEW, f->number, 0, msg);
96 } else {
97 LOG_ERR("Windows", "GetOpenFileName() failed.");
98 }
99 SetCurrentDirectoryW(dir);
100 }
101
show_messagebox(const char * caption,uint16_t caption_length,const char * message,uint16_t message_length)102 void show_messagebox(const char *caption, uint16_t caption_length, const char *message, uint16_t message_length) {
103 wchar_t message_native[message_length];
104 memset(message_native, 0, message_length);
105 utf8_to_nativestr(message, message_native, message_length * 2);
106
107 wchar_t caption_native[caption_length];
108 memset(caption_native, 0, caption_length);
109 utf8_to_nativestr(caption, caption_native, caption_length * 2);
110
111 MessageBoxW(NULL, message ? message_native : NULL, caption ? caption_native : NULL, MB_ICONWARNING);
112 }
113
openfileavatar(void)114 void openfileavatar(void) {
115 char *filepath = calloc(1, UTOX_FILE_NAME_LENGTH);
116 if (!filepath) {
117 LOG_ERR("openfileavatar", "Could not allocate memory for path.");
118 return;
119 }
120
121 wchar_t dir[UTOX_FILE_NAME_LENGTH];
122 GetCurrentDirectoryW(COUNTOF(dir), dir);
123
124 OPENFILENAME ofn = {
125 .lStructSize = sizeof(OPENFILENAME),
126 .lpstrFilter = "Supported Images\0*.GIF;*.PNG;*.JPG;*.JPEG" // TODO: add all the supported types.
127 "All Files\0*.*\0"
128 "GIF Files\0*.GIF\0"
129 "PNG Files\0*.PNG\0"
130 "JPG Files\0*.JPG;*.JPEG\0"
131 "\0",
132 .hwndOwner = main_window.window,
133 .lpstrFile = filepath,
134 .nMaxFile = UTOX_FILE_NAME_LENGTH,
135 .Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST,
136 };
137
138 while (1) { // loop until we have a good file or the user closed the dialog
139 if (!GetOpenFileName(&ofn)) {
140 LOG_TRACE("NATIVE", "GetOpenFileName() failed when trying to grab an avatar.");
141 break;
142 }
143
144 int width, height, bpp, size;
145 uint8_t *file_data = stbi_load(filepath, &width, &height, &bpp, 0);
146 uint8_t *img = stbi_write_png_to_mem(file_data, 0, width, height, bpp, &size);
147 free(file_data);
148
149 if (!img) {
150 MessageBox(NULL, (const char *)S(CANT_FIND_FILE_OR_EMPTY), NULL, MB_ICONWARNING);
151 continue;
152 }
153
154 if (size > UTOX_AVATAR_MAX_DATA_LENGTH) {
155 free(img);
156 char message[1024];
157 if (sizeof(message) < (unsigned)SLEN(AVATAR_TOO_LARGE_MAX_SIZE_IS) + 16) {
158 LOG_ERR("NATIVE", "AVATAR_TOO_LARGE message is larger than allocated buffer(%llu bytes)\n",
159 sizeof(message));
160 break;
161 }
162
163 // create message containing text that selected avatar is too large and what the max size is
164 int len = sprintf(message, "%.*s", SLEN(AVATAR_TOO_LARGE_MAX_SIZE_IS),
165 S(AVATAR_TOO_LARGE_MAX_SIZE_IS));
166 len += sprint_humanread_bytes(message + len, sizeof(message) - len, UTOX_AVATAR_MAX_DATA_LENGTH);
167 message[len++] = '\0';
168
169 MessageBox(NULL, message, NULL, MB_ICONWARNING);
170 continue;
171 }
172
173 postmessage_utox(SELF_AVATAR_SET, size, 0, img);
174 break;
175 }
176
177 free(filepath);
178 SetCurrentDirectoryW(dir);
179 }
180
file_save_inline_image_png(MSG_HEADER * msg)181 void file_save_inline_image_png(MSG_HEADER *msg) {
182 wchar_t filepath[UTOX_FILE_NAME_LENGTH] = { 0 };
183 utf8_to_nativestr((char *)msg->via.ft.name, filepath, msg->via.ft.name_length * 2);
184
185 OPENFILENAMEW ofn = {
186 .lStructSize = sizeof(OPENFILENAMEW),
187 .hwndOwner = main_window.window,
188 .lpstrFile = filepath,
189 .nMaxFile = UTOX_FILE_NAME_LENGTH,
190 .lpstrDefExt = L"png",
191 .lpstrFilter = L"PNG Files\0*.png\0",
192 .Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST,
193 };
194
195 if (GetSaveFileNameW(&ofn)) {
196 char *path = calloc(1, UTOX_FILE_NAME_LENGTH);
197 if (!path){
198 LOG_ERR("NATIVE", "Could not allocate memory for path." );
199 return;
200 }
201
202 native_to_utf8str(filepath, path, UTOX_FILE_NAME_LENGTH);
203
204 FILE *file = utox_get_file_simple(path, UTOX_FILE_OPTS_WRITE);
205 if (file) {
206 fwrite(msg->via.ft.data, msg->via.ft.data_size, 1, file);
207 fclose(file);
208
209 msg->via.ft.path = calloc(1, UTOX_FILE_NAME_LENGTH);
210 if (!msg->via.ft.path) {
211 LOG_ERR("NATIVE", "Could not allocate memory for path." );
212 free(path);
213 return;
214 }
215
216 msg->via.ft.path = (uint8_t *)strdup(path);
217 msg->via.ft.name = basename(strdup(path));
218 msg->via.ft.name_length = strlen((char *) msg->via.ft.name);
219
220 msg->via.ft.inline_png = false;
221 } else {
222 LOG_ERR("NATIVE", "file_save_inline_image_png:\tCouldn't open path: `%s` to save inline file.", path);
223 }
224
225 free(path);
226 } else {
227 LOG_ERR("NATIVE", "GetSaveFileName() failed");
228 }
229 }
230
native_save_image_png(const char * name,const uint8_t * image,const int image_size)231 bool native_save_image_png(const char *name, const uint8_t *image, const int image_size) {
232 wchar_t filepath[UTOX_FILE_NAME_LENGTH] = { 0 };
233 size_t length = strlen(name);
234 utf8_to_nativestr(name, filepath, length * 2);
235
236 OPENFILENAMEW ofn = {
237 .lStructSize = sizeof(OPENFILENAMEW),
238 .hwndOwner = main_window.window,
239 .lpstrFile = filepath,
240 .nMaxFile = UTOX_FILE_NAME_LENGTH,
241 .lpstrDefExt = L"png",
242 .lpstrFilter = L"PNG Files\0*.png\0",
243 .Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST,
244 };
245
246 if (GetSaveFileNameW(&ofn)) {
247 char *path = calloc(1, UTOX_FILE_NAME_LENGTH);
248 if (!path){
249 LOG_ERR("NATIVE", "Could not allocate memory for path.");
250 return false;
251 }
252
253 native_to_utf8str(filepath, path, UTOX_FILE_NAME_LENGTH);
254
255 FILE *file = utox_get_file_simple(path, UTOX_FILE_OPTS_WRITE);
256 if (!file) {
257 LOG_ERR("NATIVE", "Could not open file %s for write.", path);
258 free(path);
259 return false;
260 }
261
262 fwrite(image, image_size, 1, file);
263 fclose(file);
264 free(path);
265 return true;
266 }
267
268 return false;
269 }
270
postmessage_utox(UTOX_MSG msg,uint16_t param1,uint16_t param2,void * data)271 void postmessage_utox(UTOX_MSG msg, uint16_t param1, uint16_t param2, void *data) {
272 PostMessage(main_window.window, WM_TOX + (msg), ((param1) << 16) | (param2), (LPARAM)data);
273 }
274
init_ptt(void)275 void init_ptt(void) {
276 settings.push_to_talk = true;
277 }
278
check_ptt_key(void)279 bool check_ptt_key(void) {
280 if (!settings.push_to_talk) {
281 // PTT is disabled. Always send audio.
282 return true;
283 }
284
285 if (GetAsyncKeyState(VK_LCONTROL)) {
286 return true;
287 }
288
289 return false;
290 }
291
exit_ptt(void)292 void exit_ptt(void) {
293 settings.push_to_talk = false;
294 }
295
thread(void func (void *),void * args)296 void thread(void func(void *), void *args) {
297 _beginthread(func, 0, args);
298 }
299
yieldcpu(uint32_t ms)300 void yieldcpu(uint32_t ms) {
301 Sleep(ms);
302 }
303
get_time(void)304 uint64_t get_time(void) {
305 return ((uint64_t)clock() * 1000 * 1000);
306 }
307
setselection(char * UNUSED (data),uint16_t UNUSED (length))308 void setselection(char *UNUSED(data), uint16_t UNUSED(length)) {
309 // TODO: Implement.
310 }
311
copy(int value)312 void copy(int value) {
313 const uint32_t max_size = UINT16_MAX + 1;
314 char data[max_size]; //! TODO: De-hardcode this value.
315 memset(data, 0, sizeof(data));
316 int len = 0;
317
318 if (edit_active()) {
319 len = edit_copy(data, max_size - 1);
320 data[len] = 0;
321 } else if (flist_get_friend()) {
322 len = messages_selection(&messages_friend, data, max_size, value);
323 } else if (flist_get_groupchat()) {
324 len = messages_selection(&messages_group, data, max_size, value);
325 } else {
326 return;
327 }
328
329 HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * 2);
330 wchar_t *d = GlobalLock(hMem);
331 utf8tonative(data, d, len + 1); // because data is nullterminated
332 GlobalUnlock(hMem);
333 OpenClipboard(main_window.window);
334 EmptyClipboard();
335 SetClipboardData(CF_UNICODETEXT, hMem);
336 CloseClipboard();
337 }
338
339 /* TODO DRY, this exists in screen_grab.c */
create_utox_image(HBITMAP bmp,bool has_alpha,uint32_t width,uint32_t height)340 static NATIVE_IMAGE *create_utox_image(HBITMAP bmp, bool has_alpha, uint32_t width, uint32_t height) {
341 NATIVE_IMAGE *image = calloc(1, sizeof(NATIVE_IMAGE));
342 if (!image) {
343 LOG_ERR("create_utox_image", " Could not allocate memory for image." );
344 return NULL;
345 }
346
347 image->bitmap = bmp;
348 image->has_alpha = has_alpha;
349 image->width = width;
350 image->height = height;
351 image->scaled_width = width;
352 image->scaled_height = height;
353 image->stretch_mode = COLORONCOLOR;
354
355 return image;
356 }
357
358 /* TODO DRY, this exists in screen_grab.c */
sendbitmap(HDC mem,HBITMAP hbm,int width,int height)359 static void sendbitmap(HDC mem, HBITMAP hbm, int width, int height) {
360 if (width == 0 || height == 0) {
361 return;
362 }
363
364 BITMAPINFO info = {
365 .bmiHeader = {
366 .biSize = sizeof(BITMAPINFOHEADER),
367 .biWidth = width,
368 .biHeight = -(int)height,
369 .biPlanes = 1,
370 .biBitCount = 24,
371 .biCompression = BI_RGB,
372 }
373 };
374
375 void *bits = calloc(1, (width + 3) * height * 3);
376
377 GetDIBits(mem, hbm, 0, height, bits, &info, DIB_RGB_COLORS);
378
379 uint8_t pbytes = width & 3;
380 uint8_t *p = bits;
381 uint8_t *pp = bits;
382 uint8_t *end = p + width * height * 3;
383
384 while (p != end) {
385 for (int i = 0; i != width; i++) {
386 uint8_t b = pp[i * 3];
387 p[i * 3] = pp[i * 3 + 2];
388 p[i * 3 + 1] = pp[i * 3 + 1];
389 p[i * 3 + 2] = b;
390 }
391 p += width * 3;
392 pp += width * 3 + pbytes;
393 }
394
395 int size = 0;
396
397 UTOX_IMAGE out = stbi_write_png_to_mem(bits, 0, width, height, 3, &size);
398 free(bits);
399
400 NATIVE_IMAGE *image = create_utox_image(hbm, 0, width, height);
401 friend_sendimage(flist_get_friend(), image, width, height, out, size);
402 }
403
paste(void)404 void paste(void) {
405 OpenClipboard(NULL);
406 HANDLE h = GetClipboardData(CF_UNICODETEXT);
407 if (!h) {
408 h = GetClipboardData(CF_BITMAP);
409 if (h && flist_get_friend()) {
410 FRIEND *f = flist_get_friend();
411 if (!f->online) {
412 return;
413 }
414
415 BITMAP bm;
416 GetObject(h, sizeof(bm), &bm);
417
418 HDC tempdc = CreateCompatibleDC(NULL);
419 SelectObject(tempdc, h);
420
421 HBITMAP copy = CreateCompatibleBitmap(main_window.mem_DC, bm.bmWidth, bm.bmHeight);
422 SelectObject(main_window.mem_DC, copy);
423 BitBlt(main_window.mem_DC, 0, 0, bm.bmWidth, bm.bmHeight, tempdc, 0, 0, SRCCOPY);
424
425 sendbitmap(main_window.mem_DC, copy, bm.bmWidth, bm.bmHeight);
426
427 DeleteDC(tempdc);
428 }
429 } else {
430 wchar_t *d = GlobalLock(h);
431 char data[65536]; // TODO: De-hardcode this value.
432 int len = WideCharToMultiByte(CP_UTF8, 0, d, -1, data, sizeof(data), NULL, NULL);
433 if (edit_active()) {
434 edit_paste(data, len, false);
435 }
436 }
437
438 GlobalUnlock(h);
439 CloseClipboard();
440 }
441
utox_image_to_native(const UTOX_IMAGE data,size_t size,uint16_t * w,uint16_t * h,bool keep_alpha)442 NATIVE_IMAGE *utox_image_to_native(const UTOX_IMAGE data, size_t size, uint16_t *w, uint16_t *h, bool keep_alpha) {
443 int width, height, bpp;
444 uint8_t *rgba_data = stbi_load_from_memory(data, size, &width, &height, &bpp, 4);
445
446 if (rgba_data == NULL || width == 0 || height == 0) {
447 return NULL; // invalid image
448 }
449
450 BITMAPINFO bmi = {
451 .bmiHeader = {
452 .biSize = sizeof(BITMAPINFOHEADER),
453 .biWidth = width,
454 .biHeight = -height,
455 .biPlanes = 1,
456 .biBitCount = 32,
457 .biCompression = BI_RGB,
458 }
459 };
460
461 // create device independent bitmap, we can write the bytes to out
462 // to put them in the bitmap
463 uint8_t *out;
464 HBITMAP bmp = CreateDIBSection(main_window.mem_DC, &bmi, DIB_RGB_COLORS, (void **)&out, NULL, 0);
465
466 // convert RGBA data to internal format
467 // pre-applying the alpha if we're keeping the alpha channel,
468 // put the result in out
469 // NOTE: input pixels are in format RGBA, output is BGRA
470 uint8_t *p, *end = rgba_data + width * height * 4;
471 p = rgba_data;
472 if (keep_alpha) {
473 uint8_t alpha;
474 do {
475 alpha = p[3];
476 out[0] = p[2] * (alpha / 255.0); // pre-apply alpha
477 out[1] = p[1] * (alpha / 255.0);
478 out[2] = p[0] * (alpha / 255.0);
479 out[3] = alpha;
480 out += 4;
481 p += 4;
482 } while (p != end);
483 } else {
484 do {
485 out[0] = p[2];
486 out[1] = p[1];
487 out[2] = p[0];
488 out[3] = 0;
489 out += 4;
490 p += 4;
491 } while (p != end);
492 }
493
494 free(rgba_data);
495
496
497 NATIVE_IMAGE *image = create_utox_image(bmp, keep_alpha, width, height);
498
499 *w = width;
500 *h = height;
501 return image;
502 }
503
image_free(NATIVE_IMAGE * image)504 void image_free(NATIVE_IMAGE *image) {
505 if (!image) {
506 return;
507 }
508 DeleteObject(image->bitmap);
509 free(image);
510 }
511
flush_file(FILE * file)512 void flush_file(FILE *file) {
513 fflush(file);
514 int fd = _fileno(file);
515 _commit(fd);
516 }
517
ch_mod(uint8_t * UNUSED (file))518 int ch_mod(uint8_t *UNUSED(file)) {
519 /* You're probably looking for ./xlib as windows is lamesauce and wants nothing to do with sane permissions */
520 return true;
521 }
522
file_lock(FILE * file,uint64_t start,size_t length)523 int file_lock(FILE *file, uint64_t start, size_t length) {
524 OVERLAPPED lock_overlap;
525 lock_overlap.Offset = start;
526 lock_overlap.OffsetHigh = start + length;
527 lock_overlap.hEvent = 0;
528 return !LockFileEx(file, LOCKFILE_FAIL_IMMEDIATELY, 0, start, start + length, &lock_overlap);
529 }
530
file_unlock(FILE * file,uint64_t start,size_t length)531 int file_unlock(FILE *file, uint64_t start, size_t length) {
532 OVERLAPPED lock_overlap;
533 lock_overlap.Offset = start;
534 lock_overlap.OffsetHigh = start + length;
535 lock_overlap.hEvent = 0;
536 return UnlockFileEx(file, 0, start, start + length, &lock_overlap);
537 }
538
showkeyboard(bool UNUSED (show))539 void showkeyboard(bool UNUSED(show)) {} /* Added for android support. */
540
edit_will_deactivate(void)541 void edit_will_deactivate(void) {}
542
543 /* Redraws the main UI window */
redraw(void)544 void redraw(void) {
545 native_window_set_target(&main_window);
546
547 SelectObject(main_window.draw_DC, main_window.draw_BM);
548
549 panel_draw(&panel_root, 0, 0, settings.window_width, settings.window_height);
550 }
551
552 /**
553 * update_tray(void)
554 * creates a win32 NOTIFYICONDATAW struct, sets the tiptab flag, gives *hwnd,
555 * sets struct .cbSize, and resets the tibtab to native self.name;
556 */
update_tray(void)557 void update_tray(void) {
558 uint16_t length = self.name_length + sizeof(" : ") + self.statusmsg_length;
559
560 char tip[length];
561 memset(tip, 0, length);
562
563 length = snprintf(tip, length, "%.*s : %.*s",
564 self.name_length, self.name,
565 self.statusmsg_length, self.statusmsg);
566
567 NOTIFYICONDATAW nid = {
568 .uFlags = NIF_TIP,
569 .hWnd = main_window.window,
570 .cbSize = sizeof(nid),
571 };
572
573 uint16_t msg_len = safe_shrink(tip, length, MAX_TIP_LENGTH);
574 utf8_to_nativestr(tip, nid.szTip, msg_len);
575
576 Shell_NotifyIconW(NIM_MODIFY, &nid);
577 }
578
force_redraw(void)579 void force_redraw(void) {
580 redraw();
581 }
582
openurl(char * str)583 void openurl(char *str) {
584 if (try_open_tox_uri(str)) {
585 redraw();
586 return;
587 }
588
589 wchar_t url[UTOX_FILE_NAME_LENGTH] = { 0 };
590 utf8tonative(str, url, UTOX_FILE_NAME_LENGTH);
591 ShellExecuteW(NULL, L"open", url, NULL, NULL, SW_SHOW);
592 }
593
freefonts()594 void freefonts() {
595 for (size_t i = 0; i != COUNTOF(font); i++) {
596 if (font[i]) {
597 DeleteObject(font[i]);
598 }
599 }
600 }
601
loadfonts()602 void loadfonts() {
603 LOGFONT lf = {
604 .lfWeight = FW_NORMAL,
605 //.lfCharSet = ANSI_CHARSET,
606 .lfOutPrecision = OUT_TT_PRECIS,
607 .lfQuality = DEFAULT_QUALITY,
608 .lfFaceName = "DejaVu Sans",
609 };
610
611 lf.lfHeight = (SCALE(-24) - 1) / 2;
612 font[FONT_TEXT] = CreateFontIndirect(&lf);
613 lf.lfHeight = (SCALE(-22) - 1) / 2;
614 font[FONT_STATUS] = CreateFontIndirect(&lf);
615 lf.lfHeight = (SCALE(-24) - 1) / 2;
616 font[FONT_LIST_NAME] = CreateFontIndirect(&lf);
617 lf.lfWeight = FW_BOLD;
618 font[FONT_TITLE] = CreateFontIndirect(&lf);
619 lf.lfHeight = (SCALE(-28) - 1) / 2;
620 font[FONT_SELF_NAME] = CreateFontIndirect(&lf);
621 lf.lfHeight = (SCALE(-20) - 1) / 2;
622 font[FONT_MISC] = CreateFontIndirect(&lf);
623 /*lf.lfWeight = FW_NORMAL; //FW_LIGHT <- light fonts don't antialias
624 font[FONT_MSG_NAME] = CreateFontIndirect(&lf);
625 lf.lfHeight = F(11);
626 font[FONT_MSG] = CreateFontIndirect(&lf);
627 lf.lfUnderline = 1;
628 font[FONT_MSG_LINK] = CreateFontIndirect(&lf);*/
629
630 SelectObject(main_window.draw_DC, font[FONT_TEXT]);
631 TEXTMETRIC tm;
632 GetTextMetrics(main_window.draw_DC, &tm);
633 font_small_lineheight = tm.tmHeight + tm.tmExternalLeading;
634 // SelectObject(main_window.draw_DC, font[FONT_MSG]);
635 // GetTextMetrics(main_window.draw_DC, &tm);
636 // font_msg_lineheight = tm.tmHeight + tm.tmExternalLeading;
637 }
638
setscale_fonts(void)639 void setscale_fonts(void) {
640 freefonts();
641 loadfonts();
642 }
643
setscale(void)644 void setscale(void) {
645 svg_draw(1);
646 }
647
config_osdefaults(UTOX_SAVE * r)648 void config_osdefaults(UTOX_SAVE *r) {
649 r->window_x = (GetSystemMetrics(SM_CXSCREEN) - MAIN_WIDTH) / 2;
650 r->window_y = (GetSystemMetrics(SM_CYSCREEN) - MAIN_HEIGHT) / 2;
651 r->window_width = MAIN_WIDTH;
652 r->window_height = MAIN_HEIGHT;
653 }
654
655 /*
656 * CommandLineToArgvA implementation since CommandLineToArgvA doesn't exist in win32 api
657 * Limitation: nested quotation marks are not handled
658 * Credit: http://alter.org.ua/docs/win/args
659 */
CommandLineToArgvA(PCHAR CmdLine,int * _argc)660 static PCHAR *CommandLineToArgvA(PCHAR CmdLine, int *_argc) {
661 ULONG len = strlen(CmdLine);
662 ULONG i = ((len + 2) / 2) * sizeof(PVOID) + sizeof(PVOID);
663 PCHAR *argv = (PCHAR *)GlobalAlloc(GMEM_FIXED, i + (len + 2) * sizeof(CHAR));
664 PCHAR _argv = (PCHAR)(((PUCHAR)argv) + i);
665
666 ULONG argc = 0;
667 argv[argc] = _argv;
668 i = 0;
669
670 BOOLEAN in_QM = FALSE;
671 BOOLEAN in_TEXT = FALSE;
672 BOOLEAN in_SPACE = TRUE;
673
674 CHAR a;
675 ULONG j = 0;
676 while ((a = CmdLine[i])) {
677 if (in_QM) {
678 if (a == '\"') {
679 in_QM = FALSE;
680 } else {
681 _argv[j] = a;
682 j++;
683 }
684 } else {
685 switch (a) {
686 case '\"':
687 in_QM = TRUE;
688 in_TEXT = TRUE;
689 if (in_SPACE) {
690 argv[argc] = _argv + j;
691 argc++;
692 }
693 in_SPACE = FALSE;
694 break;
695 case ' ':
696 case '\t':
697 case '\n':
698 case '\r':
699 if (in_TEXT) {
700 _argv[j] = '\0';
701 j++;
702 }
703 in_TEXT = FALSE;
704 in_SPACE = TRUE;
705 break;
706 default:
707 in_TEXT = TRUE;
708 if (in_SPACE) {
709 argv[argc] = _argv + j;
710 argc++;
711 }
712 _argv[j] = a;
713 j++;
714 in_SPACE = FALSE;
715 break;
716 }
717 }
718 i++;
719 }
720 _argv[j] = '\0';
721 argv[argc] = NULL;
722
723 (*_argc) = argc;
724 return argv;
725 }
726
tray_icon_init(HWND parent,HICON icon)727 void tray_icon_init(HWND parent, HICON icon) {
728 NOTIFYICONDATA nid = {
729 .uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP,
730 .uCallbackMessage = WM_NOTIFYICON,
731 .hIcon = icon,
732 .szTip = "uTox default tooltip",
733 .hWnd = parent,
734 .cbSize = sizeof(nid),
735 };
736
737 Shell_NotifyIcon(NIM_ADD, &nid);
738 }
739
tray_icon_decon(HWND parent)740 static void tray_icon_decon(HWND parent) {
741 NOTIFYICONDATA nid = {
742 .hWnd = parent,
743 .cbSize = sizeof(nid),
744 };
745
746 Shell_NotifyIcon(NIM_DELETE, &nid);
747 }
748
cursors_init(void)749 static void cursors_init(void) {
750 cursors[CURSOR_NONE] = LoadCursor(NULL, IDC_ARROW);
751 cursors[CURSOR_HAND] = LoadCursor(NULL, IDC_HAND);
752 cursors[CURSOR_TEXT] = LoadCursor(NULL, IDC_IBEAM);
753 cursors[CURSOR_SELECT] = LoadCursor(NULL, IDC_CROSS);
754 cursors[CURSOR_ZOOM_IN] = LoadCursor(NULL, IDC_SIZEALL);
755 cursors[CURSOR_ZOOM_OUT] = LoadCursor(NULL, IDC_SIZEALL);
756 }
757
win_init_mutex(HANDLE * mutex,HINSTANCE hInstance,PSTR cmd,const char * instance_id)758 static bool win_init_mutex(HANDLE *mutex, HINSTANCE hInstance, PSTR cmd, const char *instance_id) {
759 *mutex = CreateMutex(NULL, false, instance_id);
760
761 if (!mutex) {
762 LOG_FATAL_ERR(-4, "Win Mutex", "Unable to create windows mutex.");
763 }
764
765 if (GetLastError() == ERROR_ALREADY_EXISTS) {
766 HWND window = FindWindow(TITLE, NULL);
767 if (window) {
768 COPYDATASTRUCT data = {
769 .cbData = strlen(cmd),
770 .lpData = cmd
771 };
772 SendMessage(window, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&data);
773 LOG_FATAL_ERR(-3, "Win Mutex", "Message sent.");
774 }
775 LOG_FATAL_ERR(-3, "Win Mutex", "Error getting mutex or window.");
776 }
777
778 return true;
779 }
780
781 /** client main()
782 *
783 * Main thread
784 * generates settings, loads settings from save file, generates main UI, starts
785 * tox, generates tray icon, handles client messages. Cleans up, and exits.
786 *
787 * also handles call from other apps.
788 */
WinMain(HINSTANCE hInstance,HINSTANCE UNUSED (hPrevInstance),PSTR cmd,int nCmdShow)789 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE UNUSED(hPrevInstance), PSTR cmd, int nCmdShow) {
790 pthread_mutex_init(&messages_lock, NULL);
791
792 int argc;
793 PCHAR *argv = CommandLineToArgvA(GetCommandLineA(), &argc);
794 if (!argv) {
795 printf("Init error -- CommandLineToArgvA failed.");
796 return -5;
797 }
798
799 int8_t should_launch_at_startup, set_show_window;
800 parse_args(argc, argv, &should_launch_at_startup, &set_show_window, NULL);
801 GlobalFree(argv);
802
803 char instance_id[MAX_PATH];
804 if (settings.portable_mode == true) {
805 /* force the working directory if opened with portable command */
806 const HMODULE hModule = GetModuleHandle(NULL);
807 GetModuleFileName(hModule, instance_id, MAX_PATH);
808
809 char *utox_path = strdup(instance_id);
810 char *utox_folder = dirname(utox_path);
811
812 SetCurrentDirectory(utox_folder);
813 strcpy(portable_mode_save_path, utox_folder);
814
815 free(utox_path);
816 sanitize_filename((uint8_t *)instance_id);
817 } else {
818 strcpy(instance_id, TITLE);
819 }
820
821 // We call utox_init after parse_args()
822 utox_init();
823
824 LOG_WARN("WinMain", "Normal windows build");
825
826 #ifdef GIT_VERSION
827 LOG_NOTE("WinMain", "uTox version %s \n", GIT_VERSION);
828 #endif
829
830 /* if opened with argument, check if uTox is already open and pass the argument to the existing process */
831 HANDLE utox_mutex;
832 win_init_mutex(&utox_mutex, hInstance, cmd, instance_id);
833
834 if (should_launch_at_startup == 1) {
835 launch_at_startup(1);
836 } else if (should_launch_at_startup == -1) {
837 launch_at_startup(0);
838 }
839
840 cursors_init();
841
842 native_window_init(hInstance); // Needed to generate the Windows window class we use.
843
844 screen_grab_init(hInstance);
845
846 OleInitialize(NULL);
847
848 theme_load(settings.theme);
849
850 settings.window_width = MAX((uint32_t)SCALE(MAIN_WIDTH), settings.window_width);
851 settings.window_height = MAX((uint32_t)SCALE(MAIN_HEIGHT), settings.window_height);
852
853 char pretitle[128];
854 snprintf(pretitle, 128, "%s %s (version : %s)", TITLE, SUB_TITLE, VERSION);
855 size_t title_size = strlen(pretitle) + 1;
856 wchar_t title[title_size];
857 mbstowcs(title, pretitle, title_size);
858
859 native_window_create_main(settings.window_x, settings.window_y, settings.window_width, settings.window_height);
860
861 native_notify_init(hInstance);
862
863 hdc_brush = GetStockObject(DC_BRUSH);
864
865 tray_icon_init(main_window.window, LoadIcon(hInstance, MAKEINTRESOURCE(101)));
866
867 SetBkMode(main_window.draw_DC, TRANSPARENT);
868
869 dnd_init(main_window.window);
870
871 // start tox thread (main_window.window needs to be set first)
872 thread(toxcore_thread, NULL);
873
874 // wait for tox_thread init
875 while (!tox_thread_init && !settings.save_encryption) {
876 yieldcpu(1);
877 }
878
879 if (*cmd) {
880 const int len = strlen(cmd);
881 do_tox_url((uint8_t *)cmd, len);
882 }
883
884 redraw();
885 update_tray();
886
887 /* From --set flag */
888 if (set_show_window) {
889 if (set_show_window == 1) {
890 settings.start_in_tray = false;
891 } else if (set_show_window == -1) {
892 settings.start_in_tray = true;
893 }
894 }
895
896 if (settings.start_in_tray) {
897 ShowWindow(main_window.window, SW_HIDE);
898 hidden = true;
899 } else {
900 ShowWindow(main_window.window, nCmdShow);
901 }
902
903 MSG msg;
904 while (GetMessage(&msg, NULL, 0, 0)) {
905 TranslateMessage(&msg);
906 DispatchMessage(&msg);
907 }
908
909 /* kill threads */
910 postmessage_utoxav(UTOXAV_KILL, 0, 0, NULL);
911 postmessage_toxcore(TOX_KILL, 0, 0, NULL);
912
913 /* cleanup */
914
915 tray_icon_decon(main_window.window);
916
917 // wait for tox_thread to exit
918 while (tox_thread_init) {
919 yieldcpu(10);
920 }
921
922 RECT wndrect = { 0 };
923 GetWindowRect(main_window.window, &wndrect);
924 UTOX_SAVE d = {
925 .window_x = wndrect.left < 0 ? 0 : wndrect.left,
926 .window_y = wndrect.top < 0 ? 0 : wndrect.top,
927 .window_width = (wndrect.right - wndrect.left),
928 .window_height = (wndrect.bottom - wndrect.top),
929 };
930 config_save(&d);
931
932 // TODO: This should be a non-zero value determined by a message's wParam.
933 return 0;
934 }
935