]> Pileus Git - wmpus/blob - sys-win32.c
Add desktop files
[wmpus] / sys-win32.c
1 /*
2  * Copyright (c) 2011-2012, Andy Spencer <andy753421@gmail.com>
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  */
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <ctype.h>
19 #include <search.h>
20
21 #define WIN32_LEAN_AND_MEAN
22 #define _WIN32_WINNT 0x0501
23 #include <windows.h>
24 #include <winbase.h>
25 #include <winuser.h>
26
27 #include "util.h"
28 #include "conf.h"
29 #include "sys.h"
30 #include "wm.h"
31
32 /* Configuration */
33 static int no_capture = 0;
34 static int stack      = 25;
35
36 /* Internal structures */
37 struct win_sys {
38         HWND hwnd;
39 };
40
41 typedef struct {
42         event_t ev;
43         int     vk;
44 } event_map_t;
45
46 /* Global data */
47 static int     shellhookid;
48 static void   *cache;
49 static win_t  *root;
50 static list_t *screens;
51
52 /* Conversion functions */
53 static event_map_t ev2vk[] = {
54         {EV_MOUSE1  , VK_LBUTTON },
55         {EV_MOUSE2  , VK_MBUTTON },
56         {EV_MOUSE3  , VK_RBUTTON },
57         {EV_LEFT    , VK_LEFT    },
58         {EV_RIGHT   , VK_RIGHT   },
59         {EV_UP      , VK_UP      },
60         {EV_DOWN    , VK_DOWN    },
61         {EV_HOME    , VK_HOME    },
62         {EV_END     , VK_END     },
63         {EV_PAGEUP  , VK_PRIOR   },
64         {EV_PAGEDOWN, VK_NEXT    },
65         {EV_F1      , VK_F1      },
66         {EV_F2      , VK_F2      },
67         {EV_F3      , VK_F3      },
68         {EV_F4      , VK_F4      },
69         {EV_F5      , VK_F5      },
70         {EV_F6      , VK_F6      },
71         {EV_F7      , VK_F7      },
72         {EV_F8      , VK_F8      },
73         {EV_F9      , VK_F9      },
74         {EV_F10     , VK_F10     },
75         {EV_F11     , VK_F11     },
76         {EV_F12     , VK_F12     },
77         {EV_SHIFT   , VK_SHIFT   },
78         {EV_SHIFT   , VK_LSHIFT  },
79         {EV_SHIFT   , VK_RSHIFT  },
80         {EV_CTRL    , VK_CONTROL },
81         {EV_CTRL    , VK_LCONTROL},
82         {EV_CTRL    , VK_RCONTROL},
83         {EV_ALT     , VK_MENU    },
84         {EV_ALT     , VK_LMENU   },
85         {EV_ALT     , VK_RMENU   },
86         {EV_WIN     , VK_LWIN    },
87         {EV_WIN     , VK_RWIN    },
88 };
89
90 /* - Keycodes */
91 static event_t w2ev(UINT vk)
92 {
93         event_map_t *em = map_getr(ev2vk,vk);
94         return em ? em->ev : tolower(vk);
95 }
96
97 static UINT ev2w(event_t ev)
98 {
99         event_map_t *em = map_get(ev2vk,ev);
100         return em ? em->vk : toupper(ev);
101 }
102
103 static mod_t getmod(void)
104 {
105         return (mod_t){
106                 .alt   = GetKeyState(VK_MENU)    < 0,
107                 .ctrl  = GetKeyState(VK_CONTROL) < 0,
108                 .shift = GetKeyState(VK_SHIFT)   < 0,
109                 .win   = GetKeyState(VK_LWIN)    < 0 ||
110                          GetKeyState(VK_RWIN)    < 0,
111         };
112 }
113
114 /* - Pointers */
115 static ptr_t getptr(void)
116 {
117         POINT wptr;
118         GetCursorPos(&wptr);
119         return (ptr_t){-1, -1, wptr.x, wptr.y};
120 }
121
122 /* Window functions */
123 static win_t *win_new(HWND hwnd, int checkwin)
124 {
125         if (checkwin) {
126                 char winclass[256];
127                 int exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
128                 GetClassName(hwnd, winclass, sizeof(winclass));
129                 if (!IsWindowVisible(hwnd))      return NULL; // Invisible stuff..
130                 if (!strcmp("#32770", winclass)) return NULL; // Message boxes
131                 if (GetWindow(hwnd, GW_OWNER))   return NULL; // Dialog boxes, etc
132                 if (exstyle & WS_EX_TOOLWINDOW)  return NULL; // Floating toolbars
133         }
134
135         RECT rect = {};
136         GetWindowRect(hwnd, &rect);
137         win_t *win = new0(win_t);
138         win->x         = rect.left;
139         win->y         = rect.top;
140         win->w         = rect.right  - rect.left;
141         win->h         = rect.bottom - rect.top;
142         win->sys       = new0(win_sys_t);
143         win->sys->hwnd = hwnd;
144         printf("win_new: %p = %p (%d,%d %dx%d)\n", win, hwnd,
145                         win->x, win->y, win->w, win->h);
146         return win;
147 }
148
149 static int win_cmp(const void *_a, const void *_b)
150 {
151         const win_t *a = _a, *b = _b;
152         if (a->sys->hwnd < b->sys->hwnd) return -1;
153         if (a->sys->hwnd > b->sys->hwnd) return  1;
154         return 0;
155 }
156
157 static win_t *win_find(HWND hwnd, int create)
158 {
159         if (!hwnd)
160                 return NULL;
161         //printf("win_find: %p, %d\n", dpy, (int)xid);
162         win_sys_t sys = {.hwnd=hwnd};
163         win_t     tmp = {.sys=&sys};
164         win_t **old = NULL, *new = NULL;
165         if ((old = tfind(&tmp, &cache, win_cmp)))
166                 return *old;
167         if (create && (new = win_new(hwnd,1)))
168                 tsearch(new, &cache, win_cmp);
169         return new;
170 }
171
172 static void win_remove(win_t *win)
173 {
174         tdelete(win, &cache, win_cmp);
175         free(win->sys);
176         free(win);
177 }
178
179 static win_t *win_cursor(void)
180 {
181         POINT wptr;
182         GetCursorPos(&wptr);
183         return win_find(GetAncestor(WindowFromPoint(wptr),GA_ROOT),0);
184 }
185
186 static win_t *win_focused(void)
187 {
188         return win_find(GetForegroundWindow(),0);
189 }
190
191 /* Callbacks */
192 LRESULT CALLBACK KbdProc(int msg, WPARAM wParam, LPARAM lParam)
193 {
194         KBDLLHOOKSTRUCT *st = (KBDLLHOOKSTRUCT *)lParam;
195         event_t ev = w2ev(st->vkCode);
196         mod_t mod = getmod();
197         mod.up = !!(st->flags & 0x80);
198         printf("KbdProc: %d,%x,%lx - %lx,%lx,%lx - %x,%x\n",
199                         msg, wParam, lParam,
200                         st->vkCode, st->scanCode, st->flags,
201                         ev, mod2int(mod));
202         return wm_handle_event(win_focused() ?: root, ev, mod, getptr())
203                 || CallNextHookEx(0, msg, wParam, lParam);
204 }
205
206 LRESULT CALLBACK MllProc(int msg, WPARAM wParam, LPARAM lParam)
207 {
208         event_t ev = EV_NONE;
209         mod_t  mod = getmod();
210         win_t *win = win_cursor();
211
212         /* Update modifiers */
213         switch (wParam) {
214         case WM_LBUTTONDOWN: mod.up = 0; ev = EV_MOUSE1; break;
215         case WM_LBUTTONUP:   mod.up = 1; ev = EV_MOUSE1; break;
216         case WM_RBUTTONDOWN: mod.up = 0; ev = EV_MOUSE3; break;
217         case WM_RBUTTONUP:   mod.up = 1; ev = EV_MOUSE3; break;
218         }
219
220         /* Check for focus-in/focus-out */
221         static win_t *old = NULL;
222         if (win && win != old) {
223                 wm_handle_event(old, EV_LEAVE, mod, getptr());
224                 wm_handle_event(win, EV_ENTER, mod, getptr());
225         }
226         old = win;
227
228         /* Send mouse movement event */
229         if (wParam == WM_MOUSEMOVE)
230                 return wm_handle_ptr(win_cursor(), getptr());
231         else if (ev != EV_NONE)
232                 return wm_handle_event(win_cursor(), ev, mod, getptr());
233         else
234                 return CallNextHookEx(0, msg, wParam, lParam);
235 }
236
237 LRESULT CALLBACK ShlProc(int msg, WPARAM wParam, LPARAM lParam)
238 {
239         HWND hwnd = (HWND)wParam;
240         win_t *win = NULL;
241         switch (msg) {
242         case HSHELL_REDRAW:
243         case HSHELL_WINDOWCREATED:
244                 printf("ShlProc: %p - %s\n", hwnd, msg == HSHELL_REDRAW ?
245                                 "redraw" : "window created");
246                 if (!(win = win_find(hwnd,0)))
247                         if ((win = win_find(hwnd,1)))
248                                 wm_insert(win);
249                 return 1;
250         case HSHELL_WINDOWREPLACED:
251         case HSHELL_WINDOWDESTROYED:
252                 printf("ShlProc: %p - %s\n", hwnd, msg == HSHELL_WINDOWREPLACED ?
253                                 "window replaced" : "window destroyed");
254                 if ((win = win_find(hwnd,0)) &&
255                     (win->state == ST_SHOW ||
256                      win->state == ST_SHADE)) {
257                         wm_remove(win);
258                         win_remove(win);
259                 }
260                 return 1;
261         case HSHELL_WINDOWACTIVATED:
262                 printf("ShlProc: %p - window activated\n", hwnd);
263                 // Fake button-click (causes crazy switching)
264                 //if ((win = win_find(hwnd,0)))
265                 //      wm_handle_event(win, EV_MOUSE1, MOD(), getptr());
266                 return 0;
267         default:
268                 printf("ShlProc: %p - unknown msg, %d\n", hwnd, msg);
269                 return 0;
270         }
271 }
272
273 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
274 {
275         //printf("WndProc: %d, %x, %lx\n", msg, wParam, lParam);
276         switch (msg) {
277         case WM_CREATE:  printf("WndProc: %p - create\n",  hwnd); return 0;
278         case WM_CLOSE:   printf("WndProc: %p - close\n",   hwnd); return 0;
279         case WM_DESTROY: printf("WndProc: %p - destroy\n", hwnd); return 0;
280         case WM_HOTKEY:  printf("WndProc: %p - hotkey\n",  hwnd); return 0;
281         }
282         if (msg == shellhookid)
283                 if (ShlProc(wParam, lParam, 0))
284                         return 1;
285         return DefWindowProc(hwnd, msg, wParam, lParam);
286 }
287
288 BOOL CALLBACK MonProc(HMONITOR mon, HDC dc, LPRECT rect, LPARAM _screens)
289 {
290         MONITORINFO info = {.cbSize=sizeof(MONITORINFO)};
291         GetMonitorInfo(mon, &info);
292         RECT *work = &info.rcWork;
293
294         list_t **screens = (list_t**)_screens;
295         win_t *screen = new0(win_t);
296         screen->x = work->left;
297         screen->y = work->top;
298         screen->z = !!(info.dwFlags & MONITORINFOF_PRIMARY);
299         screen->w = work->right  - work->left;
300         screen->h = work->bottom - work->top;
301         *screens = list_append(*screens, screen);
302         printf("mon_proc: %d,%d %dx%d\n",
303                 screen->x, screen->y, screen->w, screen->h);
304         return TRUE;
305 }
306
307 BOOL CALLBACK LoopProc(HWND hwnd, LPARAM user)
308 {
309         win_t *win;
310         if ((win = win_find(hwnd,1)))
311                 wm_insert(win);
312         return TRUE;
313 }
314
315 BOOL WINAPI CtrlProc(DWORD type)
316 {
317         sys_exit();
318         return TRUE;
319 }
320
321 /********************
322  * System functions *
323  ********************/
324 void sys_move(win_t *win, int x, int y, int w, int h)
325 {
326         printf("sys_move: %p - %d,%d  %dx%d\n", win, x, y, w, h);
327         win->x = x; win->y = y;
328         win->w = MAX(w,1); win->h = MAX(h,1);
329         MoveWindow(win->sys->hwnd, win->x, win->y, win->w, win->h, TRUE);
330 }
331
332 void sys_raise(win_t *win)
333 {
334         printf("sys_raise: %p\n", win);
335
336         /* See note in sys_focus */
337         DWORD oldId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
338         DWORD newId = GetCurrentThreadId();
339         AttachThreadInput(oldId, newId, TRUE);
340
341         SetWindowPos(win->sys->hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
342
343         AttachThreadInput(oldId, newId, FALSE);
344 }
345
346 void sys_focus(win_t *win)
347 {
348         printf("sys_focus: %p\n", win);
349
350         /* Windows prevents a thread from using SetForegroundInput under
351          * certain circumstances and instead flashes the windows toolbar icon.
352          * Attaching the thread input queues avoids this behavior */
353         HWND  fgWin = GetForegroundWindow();
354         if (fgWin == win->sys->hwnd)
355                 return; // already focused
356         DWORD oldId = GetWindowThreadProcessId(fgWin, NULL);
357         DWORD newId = GetCurrentThreadId();
358         if (oldId != newId)
359                 AttachThreadInput(oldId, newId, TRUE);
360
361         HWND next = GetWindow(win->sys->hwnd, GW_HWNDNEXT);
362         SetWindowPos(win->sys->hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
363         if (next)
364                 SetWindowPos(win->sys->hwnd, next, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
365
366         if (oldId != newId)
367                 AttachThreadInput(oldId, newId, FALSE);
368 }
369
370 void sys_show(win_t *win, state_t state)
371 {
372         static struct {
373                 char *str;
374                 int   cmd;
375         } map[] = {
376                 [ST_SHOW ] {"show" , SW_SHOW    },
377                 [ST_FULL ] {"full" , SW_MAXIMIZE},
378                 [ST_SHADE] {"shade", SW_SHOW    },
379                 [ST_ICON ] {"icon" , SW_MINIMIZE},
380                 [ST_HIDE ] {"hide" , SW_HIDE    },
381         };
382         if (win->state != state && win->state == ST_SHADE)
383                 SetWindowRgn(win->sys->hwnd, NULL, TRUE);
384         win->state = state;
385         printf("sys_show: %s\n", map[state].str);
386         ShowWindow(win->sys->hwnd, map[state].cmd);
387         if (state == ST_SHADE)
388                 SetWindowRgn(win->sys->hwnd, CreateRectRgn(0,0,win->w,stack), TRUE);
389 }
390
391 void sys_watch(win_t *win, event_t ev, mod_t mod)
392 {
393         (void)ev2w; // TODO
394         //printf("sys_watch: %p\n", win);
395 }
396
397 void sys_unwatch(win_t *win, event_t ev, mod_t mod)
398 {
399         (void)ev2w; // TODO
400         //printf("sys_unwatch: %p\n", win);
401 }
402
403 list_t *sys_info(win_t *win)
404 {
405         if (screens == NULL)
406                 EnumDisplayMonitors(NULL, NULL, MonProc, (LPARAM)&screens);
407         return screens;
408 }
409
410 win_t *sys_init(void)
411 {
412         HINSTANCE hInst = GetModuleHandle(NULL);
413         HWND      hwnd  = NULL;
414
415         /* Load configuration */
416         no_capture = conf_get_int("main.no-capture", no_capture);
417         stack      = conf_get_int("main.stack",      stack);
418
419         /* Setup window class */
420         WNDCLASSEX wc    = {
421                 .cbSize        = sizeof(WNDCLASSEX),
422                 .lpfnWndProc   = WndProc,
423                 .hInstance     = hInst,
424                 .lpszClassName = "wmpus_class",
425         };
426         if (!RegisterClassEx(&wc))
427                 printf("sys_init: Error Registering Class - %lu\n", GetLastError());
428
429         /* Get work area */
430         RECT rc;
431         SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
432
433         /* Create shell hook window */
434         if (!(hwnd = CreateWindowEx(0, "wmpus_class", "wmpus", 0,
435                         rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
436                         HWND_MESSAGE, NULL, hInst, NULL)))
437                 printf("sys_init: Error Creating Shell Hook Window - %lu\n", GetLastError());
438
439         /* Register shell hook */
440         BOOL (*RegisterShellHookWindow)(HWND) = (void*)GetProcAddress(
441                         GetModuleHandle("USER32.DLL"), "RegisterShellHookWindow");
442         if (!RegisterShellHookWindow)
443                 printf("sys_init: Error Finding RegisterShellHookWindow - %lu\n", GetLastError());
444         if (!RegisterShellHookWindow(hwnd))
445                 printf("sys_init: Error Registering ShellHook Window - %lu\n", GetLastError());
446         shellhookid = RegisterWindowMessage("SHELLHOOK");
447
448         /* Input hooks */
449         SetWindowsHookEx(WH_KEYBOARD_LL, KbdProc, hInst, 0);
450         SetWindowsHookEx(WH_MOUSE_LL,    MllProc, hInst, 0);
451         //SetWindowsHookEx(WH_SHELL,       ShlProc, hInst, 0);
452
453         /* Alternate ways to get input */
454         //if (!RegisterHotKey(hwnd, 123, MOD_CONTROL, VK_LBUTTON))
455         //      printf("sys_init: Error Registering Hotkey - %lu\n", GetLastError());
456         //if (!RegisterHotKey(NULL, 123, MOD_CONTROL, VK_LBUTTON))
457         //      printf("sys_init: Error Registering Hotkey - %lu\n", GetLastError());
458
459         /* Capture ctrl-c and console widnow close */
460         SetConsoleCtrlHandler(CtrlProc, TRUE);
461
462         return root = win_new(hwnd,0);
463 }
464
465 void sys_run(win_t *root)
466 {
467         MSG msg = {};
468         if (!no_capture)
469                 EnumWindows(LoopProc, 0);
470         while (GetMessage(&msg, NULL, 0, 0) > 0 &&
471                msg.message != WM_QUIT) {
472                 TranslateMessage(&msg);
473                 DispatchMessage(&msg);
474         }
475 }
476
477 void sys_exit(void)
478 {
479         PostMessage(root->sys->hwnd, WM_QUIT, 0, 0);
480 }
481
482 void sys_free(win_t *root)
483 {
484         /* I don't really care about this
485          * since I don't know how to use
486          * valgrind on win32 anyway.. */
487 }