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