454 lines
17 KiB
C
454 lines
17 KiB
C
// Pin a Window (DeskTools module): keep any window on top of all the others.
|
|
// A small blue box with a plus sign sits on the title bar of every pinned window.
|
|
// Click the box to unpin. Ctrl+Alt+P pins or unpins the active window.
|
|
|
|
#ifndef UNICODE
|
|
#define UNICODE
|
|
#endif
|
|
#ifndef _UNICODE
|
|
#define _UNICODE
|
|
#endif
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define _WIN32_WINNT 0x0A00
|
|
#include <windows.h>
|
|
#include "desktools.h"
|
|
#include <dwmapi.h>
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
|
|
#define WM_PINAT (WM_APP + 1)
|
|
#define WM_UNPINIDX (WM_APP + 2)
|
|
#define WM_REPOS (WM_APP + 3)
|
|
#define MAX_PINS 64
|
|
#define HK_TOGGLE 1
|
|
#define HK_CANCEL 2
|
|
#define TIMER_CHECK 1
|
|
#define TIMER_OSD 2
|
|
#define TIMER_FOLLOW 3
|
|
|
|
#define IDM_PICK 100
|
|
#define IDM_UNPINALL 101
|
|
#define IDM_PIN_BASE 1000
|
|
|
|
typedef struct {
|
|
BOOL used;
|
|
HWND target;
|
|
HWND badge;
|
|
BOOL wasTopmost;
|
|
BOOL hover;
|
|
} Pin;
|
|
|
|
static Pin g_pins[MAX_PINS];
|
|
static HINSTANCE g_inst;
|
|
static HWND g_main, g_osd;
|
|
static BOOL g_enabled = TRUE, g_hotkeyOk, g_picking, g_swallowUp;
|
|
static HWINEVENTHOOK g_evFg, g_evLoc, g_evMin;
|
|
static int g_dpi = 96;
|
|
static HFONT g_osdFont;
|
|
static WCHAR g_osdText[128];
|
|
|
|
static int S(int v) { return MulDiv(v, g_dpi, 96); }
|
|
static int BadgeSize(void) { return S(18); }
|
|
|
|
/* ---------------- small on-screen message ---------------- */
|
|
|
|
static void ShowOsdAt(int cx, int cy, const WCHAR *text, UINT ms) {
|
|
wcsncpy(g_osdText, text, 127); g_osdText[127] = 0;
|
|
HDC dc = GetDC(NULL);
|
|
SelectObject(dc, g_osdFont);
|
|
SIZE sz; GetTextExtentPoint32W(dc, g_osdText, (int)wcslen(g_osdText), &sz);
|
|
ReleaseDC(NULL, dc);
|
|
int w = sz.cx + S(28), h = S(40);
|
|
HRGN rgn = CreateRoundRectRgn(0, 0, w + 1, h + 1, S(12), S(12));
|
|
SetWindowRgn(g_osd, rgn, FALSE);
|
|
SetWindowPos(g_osd, HWND_TOPMOST, cx - w / 2, cy - h / 2, w, h, SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
InvalidateRect(g_osd, NULL, TRUE);
|
|
UpdateWindow(g_osd);
|
|
if (ms) SetTimer(g_main, TIMER_OSD, ms, NULL); else KillTimer(g_main, TIMER_OSD);
|
|
}
|
|
|
|
static void ShowOsdOver(HWND target, const WCHAR *text) {
|
|
RECT r;
|
|
if (target && GetWindowRect(target, &r)) ShowOsdAt((r.left + r.right) / 2, (r.top + r.bottom) / 2, text, 1100);
|
|
else { POINT p; GetCursorPos(&p); ShowOsdAt(p.x, p.y + S(40), text, 1100); }
|
|
}
|
|
|
|
static LRESULT CALLBACK OsdProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps; HDC dc = BeginPaint(h, &ps);
|
|
RECT rc; GetClientRect(h, &rc);
|
|
HBRUSH bg = CreateSolidBrush(RGB(32, 36, 44));
|
|
FillRect(dc, &rc, bg); DeleteObject(bg);
|
|
SelectObject(dc, g_osdFont);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, RGB(255, 255, 255));
|
|
DrawTextW(dc, g_osdText, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
|
|
EndPaint(h, &ps);
|
|
return 0;
|
|
}
|
|
case WM_MOUSEACTIVATE: return MA_NOACTIVATE;
|
|
case WM_NCHITTEST: return HTTRANSPARENT;
|
|
}
|
|
return DefWindowProcW(h, msg, wp, lp);
|
|
}
|
|
|
|
/* ---------------- the box-with-a-plus badge ---------------- */
|
|
|
|
static void RenderBadge(Pin *p) {
|
|
int B = BadgeSize();
|
|
HDC screen = GetDC(NULL);
|
|
BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi));
|
|
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bi.bmiHeader.biWidth = B; bi.bmiHeader.biHeight = -B;
|
|
bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB;
|
|
BYTE *px = NULL;
|
|
HDC dc = CreateCompatibleDC(screen);
|
|
HBITMAP bmp = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&px, NULL, 0);
|
|
if (bmp) {
|
|
HGDIOBJ old = SelectObject(dc, bmp);
|
|
// box: rounded square, blue fill, white edge; plus sign: white bars
|
|
int r = S(4), edge = S(1) > 1 ? S(1) : 1;
|
|
int bar = S(2) > 2 ? S(2) : 2, len = B * 5 / 9;
|
|
int c = B / 2, b0 = c - bar / 2, b1 = b0 + bar, l0 = c - len / 2, l1 = l0 + len;
|
|
int fr = p->hover ? 70 : 40, fg = p->hover ? 145 : 115, fb = p->hover ? 245 : 215;
|
|
for (int y = 0; y < B; y++) {
|
|
for (int x = 0; x < B; x++) {
|
|
int i = (y * B + x) * 4;
|
|
// distance outside the rounded corner
|
|
int dx = x < r ? r - x : (x >= B - r ? x - (B - r - 1) : 0);
|
|
int dy = y < r ? r - y : (y >= B - r ? y - (B - r - 1) : 0);
|
|
int inside = (dx * dx + dy * dy) <= r * r;
|
|
int a = 0, R = 0, G = 0, Bl = 0;
|
|
if (inside) {
|
|
int onEdge = x < edge || y < edge || x >= B - edge || y >= B - edge ||
|
|
(dx && dy && (dx * dx + dy * dy) > (r - edge) * (r - edge));
|
|
int plus = (x >= b0 && x < b1 && y >= l0 && y < l1) || (y >= b0 && y < b1 && x >= l0 && x < l1);
|
|
if (onEdge || plus) { R = G = Bl = 255; a = 255; }
|
|
else { R = fr; G = fg; Bl = fb; a = 235; }
|
|
}
|
|
px[i] = (BYTE)(Bl * a / 255); px[i + 1] = (BYTE)(G * a / 255); px[i + 2] = (BYTE)(R * a / 255); px[i + 3] = (BYTE)a;
|
|
}
|
|
}
|
|
SIZE sz = { B, B }; POINT src = { 0, 0 };
|
|
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
|
|
UpdateLayeredWindow(p->badge, screen, NULL, &sz, dc, &src, 0, &bf, ULW_ALPHA);
|
|
SelectObject(dc, old);
|
|
DeleteObject(bmp);
|
|
}
|
|
DeleteDC(dc);
|
|
ReleaseDC(NULL, screen);
|
|
}
|
|
|
|
static void PlaceBadge(Pin *p) {
|
|
if (!p->used || !p->badge) return;
|
|
HWND t = p->target;
|
|
if (!IsWindow(t)) return;
|
|
if (IsIconic(t) || !IsWindowVisible(t) || !g_enabled) {
|
|
if (IsWindowVisible(p->badge)) ShowWindow(p->badge, SW_HIDE);
|
|
return;
|
|
}
|
|
int B = BadgeSize();
|
|
RECT wr; GetWindowRect(t, &wr);
|
|
RECT fr = wr;
|
|
DwmGetWindowAttribute(t, DWMWA_EXTENDED_FRAME_BOUNDS, &fr, sizeof(fr));
|
|
RECT cb; ZeroMemory(&cb, sizeof(cb));
|
|
int x, y;
|
|
if (SUCCEEDED(DwmGetWindowAttribute(t, DWMWA_CAPTION_BUTTON_BOUNDS, &cb, sizeof(cb))) && cb.right > cb.left) {
|
|
// just to the left of the minimize / maximize / close buttons
|
|
x = wr.left + cb.left - B - S(8);
|
|
y = wr.top + cb.top + ((cb.bottom - cb.top) - B) / 2;
|
|
} else {
|
|
x = fr.right - S(150) - B;
|
|
y = fr.top + S(7);
|
|
}
|
|
if (x < fr.left + S(4)) x = fr.left + S(4);
|
|
if (y < fr.top + S(2)) y = fr.top + S(2);
|
|
SetWindowPos(p->badge, HWND_TOPMOST, x, y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
}
|
|
|
|
static Pin *PinOfBadge(HWND b) {
|
|
for (int i = 0; i < MAX_PINS; i++) if (g_pins[i].used && g_pins[i].badge == b) return &g_pins[i];
|
|
return NULL;
|
|
}
|
|
|
|
static LRESULT CALLBACK BadgeProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) {
|
|
Pin *p = PinOfBadge(h);
|
|
switch (msg) {
|
|
case WM_MOUSEACTIVATE: return MA_NOACTIVATE;
|
|
case WM_SETCURSOR: SetCursor(LoadCursorW(NULL, IDC_HAND)); return TRUE;
|
|
case WM_MOUSEMOVE:
|
|
if (p && !p->hover) {
|
|
p->hover = TRUE;
|
|
TRACKMOUSEEVENT t = { sizeof(t), TME_LEAVE, h, 0 };
|
|
TrackMouseEvent(&t);
|
|
RenderBadge(p);
|
|
}
|
|
return 0;
|
|
case WM_MOUSELEAVE:
|
|
if (p && p->hover) { p->hover = FALSE; RenderBadge(p); }
|
|
return 0;
|
|
case WM_LBUTTONUP:
|
|
if (p) PostMessageW(g_main, WM_UNPINIDX, (WPARAM)(p - g_pins), 0);
|
|
return 0;
|
|
}
|
|
return DefWindowProcW(h, msg, wp, lp);
|
|
}
|
|
|
|
/* ---------------- pin / unpin ---------------- */
|
|
|
|
static Pin *FindPin(HWND t) {
|
|
for (int i = 0; i < MAX_PINS; i++) if (g_pins[i].used && g_pins[i].target == t) return &g_pins[i];
|
|
return NULL;
|
|
}
|
|
|
|
static int CountPins(void) {
|
|
int n = 0;
|
|
for (int i = 0; i < MAX_PINS; i++) if (g_pins[i].used) n++;
|
|
return n;
|
|
}
|
|
|
|
static BOOL Pinnable(HWND h) {
|
|
if (!h || !IsWindow(h) || !IsWindowVisible(h)) return FALSE;
|
|
DWORD pid = 0; GetWindowThreadProcessId(h, &pid);
|
|
if (pid == GetCurrentProcessId()) return FALSE;
|
|
WCHAR cls[64]; GetClassNameW(h, cls, 64);
|
|
if (!wcscmp(cls, L"Progman") || !wcscmp(cls, L"WorkerW") || !wcscmp(cls, L"Shell_TrayWnd") ||
|
|
!wcscmp(cls, L"Shell_SecondaryTrayWnd"))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static void Unpin(Pin *p, BOOL quiet) {
|
|
if (!p->used) return;
|
|
HWND t = p->target;
|
|
if (IsWindow(t) && !p->wasTopmost)
|
|
SetWindowPos(t, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
if (p->badge) DestroyWindow(p->badge);
|
|
p->used = FALSE; p->badge = NULL;
|
|
if (!quiet && IsWindow(t)) ShowOsdOver(t, L"Unpinned");
|
|
}
|
|
|
|
static void PinWindow(HWND h) {
|
|
h = GetAncestor(h, GA_ROOT);
|
|
if (!Pinnable(h)) return;
|
|
Pin *existing = FindPin(h);
|
|
if (existing) { Unpin(existing, FALSE); return; }
|
|
Pin *p = NULL;
|
|
for (int i = 0; i < MAX_PINS; i++) if (!g_pins[i].used) { p = &g_pins[i]; break; }
|
|
if (!p) return;
|
|
ZeroMemory(p, sizeof(*p));
|
|
p->target = h;
|
|
p->wasTopmost = (GetWindowLongW(h, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
|
|
SetWindowPos(h, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
if (!(GetWindowLongW(h, GWL_EXSTYLE) & WS_EX_TOPMOST)) {
|
|
ShowOsdOver(h, L"Can't pin this window (admin program?)");
|
|
return;
|
|
}
|
|
p->badge = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
|
|
L"DeskToolsPinBadge", L"", WS_POPUP, 0, 0, BadgeSize(), BadgeSize(),
|
|
NULL, NULL, g_inst, NULL);
|
|
p->used = TRUE;
|
|
RenderBadge(p);
|
|
PlaceBadge(p);
|
|
ShowOsdOver(h, L"Pinned on top");
|
|
}
|
|
|
|
static void UnpinAll(BOOL quiet) {
|
|
for (int i = 0; i < MAX_PINS; i++) if (g_pins[i].used) Unpin(&g_pins[i], quiet);
|
|
}
|
|
|
|
/* ---------------- picking a window with the mouse ---------------- */
|
|
|
|
static void StopPicking(void) {
|
|
if (!g_picking) return;
|
|
g_picking = FALSE;
|
|
KillTimer(g_main, TIMER_FOLLOW);
|
|
UnregisterHotKey(g_main, HK_CANCEL);
|
|
ShowWindow(g_osd, SW_HIDE);
|
|
}
|
|
|
|
static void StartPicking(void) {
|
|
if (!g_enabled) return;
|
|
g_picking = TRUE;
|
|
RegisterHotKey(g_main, HK_CANCEL, 0, VK_ESCAPE);
|
|
SetTimer(g_main, TIMER_FOLLOW, 30, NULL);
|
|
POINT pt; GetCursorPos(&pt);
|
|
ShowOsdAt(pt.x, pt.y + S(44), L"Click a window to pin it on top (Esc cancels)", 0);
|
|
}
|
|
|
|
int PW_Mouse(WPARAM wp, MSLLHOOKSTRUCT *m) {
|
|
if (!g_picking) {
|
|
if (g_swallowUp && wp == WM_LBUTTONUP) { g_swallowUp = FALSE; return 1; }
|
|
return 0;
|
|
}
|
|
if (wp == WM_LBUTTONDOWN) {
|
|
PostMessageW(g_main, WM_PINAT, 0, MAKELPARAM((WORD)(SHORT)m->pt.x, (WORD)(SHORT)m->pt.y));
|
|
g_swallowUp = TRUE;
|
|
return 1;
|
|
}
|
|
if (wp == WM_RBUTTONDOWN || wp == WM_RBUTTONUP) {
|
|
PostMessageW(g_main, WM_PINAT, 1, 0); // right-click cancels
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------- keeping badges in place ---------------- */
|
|
|
|
static void CALLBACK WinEventProc(HWINEVENTHOOK hook, DWORD ev, HWND h, LONG obj, LONG child, DWORD tid, DWORD time) {
|
|
(void)hook; (void)child; (void)tid; (void)time;
|
|
if (ev == EVENT_SYSTEM_FOREGROUND) {
|
|
// a pinned window that comes to the front would cover its badge, so lift the badges back up
|
|
PostMessageW(g_main, WM_REPOS, 0, 0);
|
|
return;
|
|
}
|
|
if (obj != OBJID_WINDOW || !h) return;
|
|
Pin *p = FindPin(h);
|
|
if (p) PlaceBadge(p);
|
|
}
|
|
|
|
static LRESULT CALLBACK MainProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_HOTKEY:
|
|
if (wp == HK_TOGGLE) {
|
|
HWND fg = GetForegroundWindow();
|
|
if (fg) PinWindow(fg);
|
|
} else if (wp == HK_CANCEL) {
|
|
StopPicking();
|
|
}
|
|
return 0;
|
|
case WM_PINAT:
|
|
StopPicking();
|
|
if (wp == 0) {
|
|
POINT pt = { (short)LOWORD(lp), (short)HIWORD(lp) };
|
|
HWND w = WindowFromPoint(pt);
|
|
if (w) PinWindow(w);
|
|
}
|
|
return 0;
|
|
case WM_UNPINIDX:
|
|
if (wp < MAX_PINS) Unpin(&g_pins[wp], FALSE);
|
|
return 0;
|
|
case WM_REPOS:
|
|
for (int i = 0; i < MAX_PINS; i++) if (g_pins[i].used) PlaceBadge(&g_pins[i]);
|
|
return 0;
|
|
case WM_TIMER:
|
|
if (wp == TIMER_OSD) { KillTimer(h, TIMER_OSD); if (!g_picking) ShowWindow(g_osd, SW_HIDE); }
|
|
else if (wp == TIMER_FOLLOW && g_picking) {
|
|
POINT pt; GetCursorPos(&pt);
|
|
RECT r; GetWindowRect(g_osd, &r);
|
|
SetWindowPos(g_osd, HWND_TOPMOST, pt.x - (r.right - r.left) / 2, pt.y + S(24), 0, 0,
|
|
SWP_NOSIZE | SWP_NOACTIVATE);
|
|
} else if (wp == TIMER_CHECK) {
|
|
for (int i = 0; i < MAX_PINS; i++) {
|
|
Pin *p = &g_pins[i];
|
|
if (!p->used) continue;
|
|
if (!IsWindow(p->target)) { if (p->badge) DestroyWindow(p->badge); p->used = FALSE; continue; }
|
|
// something else may have dropped the window off the top; put it back
|
|
if (!(GetWindowLongW(p->target, GWL_EXSTYLE) & WS_EX_TOPMOST))
|
|
SetWindowPos(p->target, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
PlaceBadge(p);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_COMMAND: {
|
|
UINT id = LOWORD(wp);
|
|
if (id >= IDM_PIN_BASE && id < IDM_PIN_BASE + MAX_PINS) { Unpin(&g_pins[id - IDM_PIN_BASE], FALSE); return 0; }
|
|
if (id == IDM_PICK) StartPicking();
|
|
else if (id == IDM_UNPINALL) UnpinAll(TRUE);
|
|
return 0;
|
|
}
|
|
case WM_QUERYENDSESSION:
|
|
UnpinAll(TRUE);
|
|
return TRUE;
|
|
case WM_DESTROY:
|
|
UnpinAll(TRUE);
|
|
return 0;
|
|
}
|
|
return DefWindowProcW(h, msg, wp, lp);
|
|
}
|
|
|
|
/* ---------------- DeskTools module interface ---------------- */
|
|
|
|
void PW_SetEnabled(BOOL on) {
|
|
g_enabled = on;
|
|
if (on) {
|
|
if (!g_hotkeyOk) g_hotkeyOk = RegisterHotKey(g_main, HK_TOGGLE, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 'P');
|
|
} else {
|
|
StopPicking();
|
|
UnpinAll(TRUE);
|
|
if (g_hotkeyOk) { UnregisterHotKey(g_main, HK_TOGGLE); g_hotkeyOk = FALSE; }
|
|
}
|
|
}
|
|
BOOL PW_IsEnabled(void) { return g_enabled; }
|
|
|
|
void PW_Init(HINSTANCE inst, BOOL enabled) {
|
|
g_inst = inst;
|
|
HDC sdc = GetDC(NULL); g_dpi = GetDeviceCaps(sdc, LOGPIXELSY); ReleaseDC(NULL, sdc);
|
|
if (g_dpi < 96) g_dpi = 96;
|
|
g_osdFont = CreateFontW(-MulDiv(11, g_dpi, 72), 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
|
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
|
|
|
|
WNDCLASSEXW wc; ZeroMemory(&wc, sizeof(wc));
|
|
wc.cbSize = sizeof(wc); wc.lpfnWndProc = MainProc; wc.hInstance = inst; wc.lpszClassName = L"DeskToolsPinMain";
|
|
RegisterClassExW(&wc);
|
|
WNDCLASSEXW bc; ZeroMemory(&bc, sizeof(bc));
|
|
bc.cbSize = sizeof(bc); bc.lpfnWndProc = BadgeProc; bc.hInstance = inst; bc.lpszClassName = L"DeskToolsPinBadge";
|
|
bc.hCursor = LoadCursorW(NULL, IDC_HAND);
|
|
RegisterClassExW(&bc);
|
|
WNDCLASSEXW oc; ZeroMemory(&oc, sizeof(oc));
|
|
oc.cbSize = sizeof(oc); oc.lpfnWndProc = OsdProc; oc.hInstance = inst; oc.lpszClassName = L"DeskToolsPinOSD";
|
|
RegisterClassExW(&oc);
|
|
|
|
g_main = CreateWindowExW(0, L"DeskToolsPinMain", L"Pin a Window", 0, 0, 0, 0, 0, NULL, NULL, inst, NULL);
|
|
g_osd = CreateWindowExW(WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT,
|
|
L"DeskToolsPinOSD", L"", WS_POPUP, 0, 0, 10, 10, NULL, NULL, inst, NULL);
|
|
SetLayeredWindowAttributes(g_osd, 0, 230, LWA_ALPHA);
|
|
|
|
g_evFg = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
|
|
g_evLoc = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
|
|
g_evMin = SetWinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
|
|
SetTimer(g_main, TIMER_CHECK, 500, NULL);
|
|
g_enabled = !enabled;
|
|
PW_SetEnabled(enabled);
|
|
}
|
|
|
|
void PW_Shutdown(void) {
|
|
if (g_evFg) UnhookWinEvent(g_evFg);
|
|
if (g_evLoc) UnhookWinEvent(g_evLoc);
|
|
if (g_evMin) UnhookWinEvent(g_evMin);
|
|
g_evFg = g_evLoc = g_evMin = NULL;
|
|
if (g_main) { DestroyWindow(g_main); g_main = NULL; }
|
|
if (g_osd) { DestroyWindow(g_osd); g_osd = NULL; }
|
|
if (g_osdFont) { DeleteObject(g_osdFont); g_osdFont = NULL; }
|
|
}
|
|
|
|
void PW_AppendMenu(HMENU parent, UINT base) {
|
|
HMENU m = CreatePopupMenu();
|
|
UINT dis = g_enabled ? 0 : MF_GRAYED;
|
|
AppendMenuW(m, MF_STRING | (g_enabled ? MF_CHECKED : 0), base + DT_ID_ENABLE, L"Turned on");
|
|
AppendMenuW(m, MF_SEPARATOR, 0, NULL);
|
|
AppendMenuW(m, MF_STRING | dis, base + IDM_PICK, L"Pin a window...\tCtrl+Alt+P");
|
|
int n = 0;
|
|
for (int i = 0; i < MAX_PINS; i++) {
|
|
Pin *p = &g_pins[i];
|
|
if (!p->used || !IsWindow(p->target)) continue;
|
|
WCHAR title[80], item[128];
|
|
GetWindowTextW(p->target, title, 60);
|
|
if (!title[0]) wcscpy(title, L"(untitled)");
|
|
swprintf(item, 128, L"Unpin: %ls", title);
|
|
AppendMenuW(m, MF_STRING, base + IDM_PIN_BASE + i, item);
|
|
n++;
|
|
}
|
|
AppendMenuW(m, MF_STRING | (n ? 0 : MF_GRAYED), base + IDM_UNPINALL, L"Unpin all windows");
|
|
WCHAR label[64];
|
|
if (n) swprintf(label, 64, L"Pin a Window (%d pinned)", n); else wcscpy(label, L"Pin a Window");
|
|
AppendMenuW(parent, MF_POPUP, (UINT_PTR)m, label);
|
|
(void)CountPins;
|
|
}
|
|
|
|
void PW_Command(UINT local) {
|
|
if (local == DT_ID_ENABLE) { PW_SetEnabled(!g_enabled); return; }
|
|
SendMessageW(g_main, WM_COMMAND, local, 0);
|
|
}
|