1 #include "events.h"
2 
3 #include "main.h"
4 #include "window.h"
5 
6 #include "../commands.h"
7 #include "../debug.h"
8 #include "../flist.h"
9 #include "../friend.h"
10 #include "../macros.h"
11 #include "../self.h"
12 #include "../settings.h"
13 #include "../theme.h"
14 #include "../tox.h"
15 #include "../utox.h"
16 
17 #include "../av/utox_av.h"
18 
19 #include "../native/clipboard.h"
20 #include "../native/keyboard.h"
21 #include "../native/notify.h"
22 #include "../native/ui.h"
23 
24 #include "../ui/dropdown.h"
25 #include "../ui/edit.h"
26 #include "../ui/svg.h"
27 
28 #include "../layout/background.h"
29 #include "../layout/notify.h"
30 #include "../layout/settings.h"
31 
32 #include <windowsx.h>
33 
34 #include "../main.h" // main_width
35 
36 static TRACKMOUSEEVENT tme = {
37     sizeof(TRACKMOUSEEVENT),
38     TME_LEAVE,
39     0,
40     0,
41 };
42 
43 static bool mouse_tracked = false;
44 
45 /** Toggles the main window to/from hidden to tray/shown. */
togglehide(int show)46 static void togglehide(int show) {
47     if (hidden || show) {
48         ShowWindow(main_window.window, SW_RESTORE);
49         SetForegroundWindow(main_window.window);
50         redraw();
51         hidden = false;
52     } else {
53         ShowWindow(main_window.window, SW_HIDE);
54         hidden = true;
55     }
56 }
57 
58 /** Right click context menu for the tray icon */
ShowContextMenu(void)59 static void ShowContextMenu(void) {
60     POINT pt;
61     GetCursorPos(&pt);
62     HMENU hMenu = CreatePopupMenu();
63     if (hMenu) {
64         InsertMenu(hMenu, -1, MF_BYPOSITION, TRAY_SHOWHIDE, hidden ? "Restore" : "Hide");
65 
66         InsertMenu(hMenu, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
67 
68         InsertMenu(hMenu, -1, MF_BYPOSITION | ((self.status == TOX_USER_STATUS_NONE) ? MF_CHECKED : 0),
69                    TRAY_STATUS_AVAILABLE, "Available");
70         InsertMenu(hMenu, -1, MF_BYPOSITION | ((self.status == TOX_USER_STATUS_AWAY) ? MF_CHECKED : 0),
71                    TRAY_STATUS_AWAY, "Away");
72         InsertMenu(hMenu, -1, MF_BYPOSITION | ((self.status == TOX_USER_STATUS_BUSY) ? MF_CHECKED : 0),
73                    TRAY_STATUS_BUSY, "Busy");
74 
75         InsertMenu(hMenu, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
76 
77         InsertMenu(hMenu, -1, MF_BYPOSITION, TRAY_EXIT, "Exit");
78 
79         // note:    must set window to the foreground or the
80         //          menu won't disappear when it should
81         SetForegroundWindow(main_window.window);
82 
83         TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, main_window.window, NULL);
84         DestroyMenu(hMenu);
85     }
86 }
87 
88 /* TODO should this be moved to window.c? */
move_window(int x,int y)89 static void move_window(int x, int y){
90     LOG_TRACE("Win events", "delta x == %i\n", x);
91     LOG_TRACE("Win events", "delta y == %i\n", y);
92     SetWindowPos(main_window.window, 0, main_window._.x + x, main_window._.y + y, 0, 0,
93                           SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);
94     main_window._.x += x;
95     main_window._.y += y;
96 }
97 
98 #define setstatus(x)                                         \
99     if (self.status != x) {                                  \
100         postmessage_toxcore(TOX_SELF_SET_STATE, x, 0, NULL); \
101         self.status = x;                                     \
102         redraw();                                            \
103     }
104 
105 /** Handles all callback requests from winmain();
106  *
107  * handles the window functions internally, and ships off the tox calls to tox
108  */
WindowProc(HWND window,UINT msg,WPARAM wParam,LPARAM lParam)109 LRESULT CALLBACK WindowProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
110     static int mx, my;
111     static bool mdown = false;
112     static int mdown_x, mdown_y;
113     static uint32_t taskbar_created;
114 
115     if (main_window.window && window != main_window.window) {
116         if (msg == WM_DESTROY) {
117             if (window == preview_hwnd) {
118                 if (settings.video_preview) {
119                     settings.video_preview = false;
120                     postmessage_utoxav(UTOXAV_STOP_VIDEO, UINT16_MAX, 0, NULL);
121                 }
122 
123                 return false;
124             }
125 
126             for (uint8_t i = 0; i < self.friend_list_count; i++) {
127                 if (video_hwnd[i] == window) {
128                     FRIEND *f = get_friend(i);
129                     postmessage_utoxav(UTOXAV_STOP_VIDEO, f->number, 0, NULL);
130                     break;
131                 }
132             }
133         }
134 
135         LOG_TRACE("WinEvent", "Uncaught event %u & %u", wParam, lParam);
136         return DefWindowProcW(window, msg, wParam, lParam);
137     }
138 
139     switch (msg) {
140         case WM_QUIT:
141         case WM_CLOSE:
142         case WM_DESTROY: {
143             if (settings.close_to_tray) {
144                 LOG_INFO("Events", "Closing to tray." );
145                 togglehide(0);
146                 return true;
147             } else {
148                 PostQuitMessage(0);
149                 return false;
150             }
151         }
152 
153         case WM_GETMINMAXINFO: {
154             POINT min = { SCALE(MAIN_WIDTH), SCALE(MAIN_HEIGHT) };
155             ((MINMAXINFO *)lParam)->ptMinTrackSize = min;
156 
157             break;
158         }
159 
160         case WM_CREATE: {
161             LOG_INFO("Windows", "WM_CREATE");
162             taskbar_created = RegisterWindowMessage(TEXT("TaskbarCreated"));
163             return false;
164         }
165 
166         case WM_SIZE: {
167             switch (wParam) {
168                 case SIZE_MAXIMIZED: {
169                     settings.window_maximized = true;
170                     break;
171                 }
172 
173                 case SIZE_RESTORED: {
174                     settings.window_maximized = false;
175                     break;
176                 }
177             }
178 
179             int w = GET_X_LPARAM(lParam);
180             int h = GET_Y_LPARAM(lParam);
181 
182             if (w != 0) {
183                 RECT r;
184                 GetClientRect(window, &r);
185                 w = r.right;
186                 h = r.bottom;
187 
188                 settings.window_width  = w;
189                 settings.window_height = h;
190 
191                 ui_rescale(dropdown_dpi.selected + 5);
192                 ui_size(w, h);
193 
194                 if (main_window.draw_BM) {
195                     DeleteObject(main_window.draw_BM);
196                 }
197 
198                 main_window.draw_BM = CreateCompatibleBitmap(main_window.window_DC, settings.window_width,
199                                                              settings.window_height);
200                 SelectObject(main_window.window_DC, main_window.draw_BM);
201                 redraw();
202             }
203             break;
204         }
205 
206         case WM_SETFOCUS: {
207             if (flashing) {
208                 FlashWindow(main_window.window, false);
209                 flashing = false;
210 
211                 NOTIFYICONDATAW nid = {
212                     .uFlags = NIF_ICON,
213                     .hWnd   = main_window.window,
214                     .hIcon  = black_icon,
215                     .cbSize = sizeof(nid),
216                 };
217 
218                 Shell_NotifyIconW(NIM_MODIFY, &nid);
219             }
220 
221             have_focus = true;
222             break;
223         }
224 
225         case WM_KILLFOCUS: {
226             have_focus = false;
227             break;
228         }
229 
230         case WM_ERASEBKGND: {
231             return true;
232         }
233 
234         case WM_PAINT: {
235             PAINTSTRUCT ps;
236 
237             BeginPaint(window, &ps);
238 
239             RECT r = ps.rcPaint;
240             BitBlt(main_window.window_DC, r.left, r.top, r.right - r.left, r.bottom - r.top,
241                    main_window.draw_DC, r.left, r.top, SRCCOPY);
242 
243             EndPaint(window, &ps);
244             return false;
245         }
246 
247         case WM_SYSKEYDOWN: // called instead of WM_KEYDOWN when ALT is down or F10 is pressed
248         case WM_KEYDOWN: {
249             bool control = (GetKeyState(VK_CONTROL) & 0x80) != 0;
250             bool shift   = (GetKeyState(VK_SHIFT) & 0x80) != 0;
251             bool alt     = (GetKeyState(VK_MENU) & 0x80) != 0; /* Be careful not to clobber alt+num symbols */
252 
253             if (wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9) {
254                 // normalize keypad and non-keypad numbers
255                 wParam = wParam - VK_NUMPAD0 + '0';
256             }
257 
258             if (control && wParam == 'C') {
259                 copy(1);
260                 return false;
261             }
262 
263             if (control) {
264                 if ((wParam == VK_TAB && shift) || wParam == VK_PRIOR) {
265                     flist_previous_tab();
266                     redraw();
267                     return false;
268                 } else if (wParam == VK_TAB || wParam == VK_NEXT) {
269                     flist_next_tab();
270                     redraw();
271                     return false;
272                 }
273             }
274 
275             if (control && !alt) {
276                 if (wParam >= '1' && wParam <= '9') {
277                     flist_selectchat(wParam - '1');
278                     redraw();
279                     return false;
280                 } else if (wParam == '0') {
281                     flist_selectchat(9);
282                     redraw();
283                     return false;
284                 }
285             }
286 
287             if (edit_active()) {
288                 if (control) {
289                     switch (wParam) {
290                         case 'V':
291                             paste();
292                             return false;
293                         case 'X':
294                             copy(0);
295                             edit_char(KEY_DEL, 1, 0);
296                             return false;
297                     }
298                 }
299 
300                 if (control || ((wParam < 'A' || wParam > 'Z') && wParam != VK_RETURN && wParam != VK_BACK)) {
301                     edit_char(wParam, 1, (control << 2) | shift);
302                 }
303             } else {
304                 messages_char(wParam);
305                 redraw(); // TODO maybe if this
306                 break;
307             }
308 
309             break;
310         }
311 
312         case WM_CHAR: {
313             if (edit_active()) {
314                 if (wParam == KEY_RETURN && (GetKeyState(VK_SHIFT) & 0x80)) {
315                     wParam = '\n';
316                 }
317 
318                 if (wParam != KEY_TAB) {
319                     edit_char(wParam, 0, 0);
320                 }
321             }
322 
323             return false;
324         }
325 
326         case WM_MOUSEWHEEL: {
327             double delta = (double)GET_WHEEL_DELTA_WPARAM(wParam);
328             mx           = GET_X_LPARAM(lParam);
329             my           = GET_Y_LPARAM(lParam);
330 
331             panel_mwheel(&panel_root, mx, my, settings.window_width, settings.window_height,
332                          delta / (double)(WHEEL_DELTA), 1);
333             return false;
334         }
335 
336         case WM_MOUSEMOVE: {
337             int x, y, dx, dy;
338 
339             x = GET_X_LPARAM(lParam);
340             y = GET_Y_LPARAM(lParam);
341 
342             dx = x - mx;
343             dy = y - my;
344             mx = x;
345             my = y;
346 
347 
348             if (btn_move_window_down) {
349                 move_window(x - mdown_x, y - mdown_y);
350             }
351 
352             cursor = 0;
353             panel_mmove(&panel_root, 0, 0, settings.window_width, settings.window_height, x, y, dx, dy);
354 
355             SetCursor(cursors[cursor]);
356 
357             if (!mouse_tracked) {
358                 TrackMouseEvent(&tme);
359                 mouse_tracked = true;
360             }
361 
362             return false;
363         }
364 
365         case WM_LBUTTONDOWN: {
366             mdown_x = GET_X_LPARAM(lParam);
367             mdown_y = GET_Y_LPARAM(lParam);
368             // Intentional fall through to save the original mdown location.
369         }
370         case WM_LBUTTONDBLCLK: {
371             mdown = true;
372 
373             int x = GET_X_LPARAM(lParam);
374             int y = GET_Y_LPARAM(lParam);
375 
376             if (x != mx || y != my) {
377                 panel_mmove(&panel_root, 0, 0, settings.window_width, settings.window_height, x, y, x - mx, y - my);
378                 mx = x;
379                 my = y;
380             }
381 
382             // double redraw>
383             panel_mdown(&panel_root);
384             if (msg == WM_LBUTTONDBLCLK) {
385                 panel_dclick(&panel_root, 0);
386             }
387 
388             SetCapture(window);
389             break;
390         }
391 
392         case WM_RBUTTONDOWN: {
393             panel_mright(&panel_root);
394             break;
395         }
396 
397         case WM_RBUTTONUP: {
398             break;
399         }
400 
401         case WM_LBUTTONUP: {
402             ReleaseCapture();
403             break;
404         }
405 
406         case WM_CAPTURECHANGED: {
407             if (mdown) {
408                 panel_mup(&panel_root);
409                 mdown = false;
410             }
411 
412             break;
413         }
414 
415         case WM_MOUSELEAVE: {
416             ui_mouseleave();
417             mouse_tracked = false;
418             btn_move_window_down = false;
419             LOG_TRACE("Win events", "mouse leave\n");
420             break;
421         }
422 
423 
424         case WM_COMMAND: {
425             int menu = LOWORD(wParam); //, msg = HIWORD(wParam);
426 
427             switch (menu) {
428                 case TRAY_SHOWHIDE: {
429                     togglehide(0);
430                     break;
431                 }
432 
433                 case TRAY_EXIT: {
434                     PostQuitMessage(0);
435                     break;
436                 }
437 
438                 case TRAY_STATUS_AVAILABLE: {
439                     setstatus(TOX_USER_STATUS_NONE);
440                     break;
441                 }
442 
443                 case TRAY_STATUS_AWAY: {
444                     setstatus(TOX_USER_STATUS_AWAY);
445                     break;
446                 }
447 
448                 case TRAY_STATUS_BUSY: {
449                     setstatus(TOX_USER_STATUS_BUSY);
450                     break;
451                 }
452             }
453 
454             break;
455         }
456 
457         case WM_NOTIFYICON: {
458             int message = LOWORD(lParam);
459 
460             switch (message) {
461                 case WM_MOUSEMOVE: {
462                     break;
463                 }
464 
465                 case WM_LBUTTONDOWN: {
466                     togglehide(0);
467                     break;
468                 }
469 
470                 case WM_LBUTTONDBLCLK: {
471                     togglehide(1);
472                     break;
473                 }
474 
475                 case WM_LBUTTONUP: {
476                     break;
477                 }
478 
479                 case WM_RBUTTONDOWN: {
480                     break;
481                 }
482 
483                 case WM_RBUTTONUP:
484                 case WM_CONTEXTMENU: {
485                     ShowContextMenu();
486                     break;
487                 }
488             }
489 
490             return false;
491         }
492 
493         case WM_COPYDATA: {
494             togglehide(1);
495             SetForegroundWindow(window);
496             COPYDATASTRUCT *data = (void *)lParam;
497             if (data->lpData) {
498                 do_tox_url(data->lpData, data->cbData);
499             }
500 
501             return false;
502         }
503 
504         case WM_TOX ... WM_TOX + 128: {
505             utox_message_dispatch(msg - WM_TOX, wParam >> 16, wParam, (void *)lParam);
506             return false;
507         }
508 
509         default: {
510             if (msg == taskbar_created) {
511                 tray_icon_init(main_window.window, black_icon);
512             }
513             break;
514         }
515     }
516 
517     return DefWindowProcW(window, msg, wParam, lParam);
518 }
519