From 88972090ee2fed523ec11fb730391f055393eb2a Mon Sep 17 00:00:00 2001 From: weishaupt Date: Sun, 4 Oct 2026 17:14:33 +0000 Subject: [PATCH] Add Pin a Window source, icon, README and build script --- README.md | 46 ++++++ app.ico | Bin 0 -> 32314 bytes build.sh | 7 + pw.c | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ res.rc | 23 +++ 5 files changed, 529 insertions(+) create mode 100644 README.md create mode 100644 app.ico create mode 100644 build.sh create mode 100644 pw.c create mode 100644 res.rc diff --git a/README.md b/README.md new file mode 100644 index 0000000..894df07 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# DeskTools + +Four small Windows desktop tools in one tray program (about 190 KB, no installer). + +Run `DeskTools.exe`. It lives in the system tray. Right-click the icon for each tool's menu, "Start with Windows" and a help screen. Each tool can be turned off from its own submenu. Exiting DeskTools puts every window back to normal. + +## DeskIcons: send a window to the desktop + +Shift+click a window's minimize button, or press Ctrl+Alt+I. The window leaves the taskbar and shows up on the desktop as a label with its title. Drag the label anywhere, double-click it to bring the window back, or right-click it to restore or close the window. + +## SeeThru: make a window see-through + +Hold Ctrl+Shift and scroll over any window to change its opacity in 5% steps, down to 5%. On the active window, Ctrl+Alt+Page Down makes it more see-through, Ctrl+Alt+Page Up more solid, and Ctrl+Alt+Home fully solid again. Ctrl+Alt+End turns on ghost mode: the window stays on top and clicks pass straight through it. + +## QuickNote: type on the desktop + +Double-click any empty spot on the wallpaper and start typing. The text stays on the desktop with no box or border, behind your windows like desktop icons. Drag across text to select it and press Ctrl+C to copy. Drag the small gray square beside a note's first line to move it. Right-click a note to copy, paste, change its color or delete it. Notes save automatically. + +## Pin a Window: keep a window on top + +Press Ctrl+Alt+P to pin or unpin the active window, or choose "Pin a window..." from the tray menu and click any window. A small blue box with a plus sign appears on the pinned window's title bar. Click the box to unpin it. + +## Notes + +- Windows from programs running as administrator can only be changed if DeskTools also runs as administrator. +- The exe is unsigned, so Windows SmartScreen may warn the first time. Choose "More info" and then "Run anyway." +- DeskTools replaces the separate DeskIcons, SeeThru and QuickNote apps. Exit those before starting it. + +## Building + +Plain Win32 C, no dependencies beyond the standard Windows DLLs. + +``` +sh build.sh +``` + +Requires MinGW-w64 (`x86_64-w64-mingw32-gcc` and `windres`). + +| File | What it is | +|---|---| +| `desktools.c` | Tray icon, menu, shared mouse hook, start-up | +| `desktools.h` | Shared declarations | +| `di.c` | DeskIcons | +| `st.c` | SeeThru | +| `qn.c` | QuickNote | +| `pw.c` | Pin a Window | diff --git a/app.ico b/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d6e51cb7752fdb5d5a2d9ad2c310c08743ccb36 GIT binary patch literal 32314 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 VNXf^&eWneD7cUNl{%^OE{XZN5y0icQ literal 0 HcmV?d00001 diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..95d7ddf --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Cross-compile DeskTools.exe with MinGW-w64 (Linux) or MSYS2 (Windows). +set -e +x86_64-w64-mingw32-windres res.rc -O coff -o res.o +x86_64-w64-mingw32-gcc -Os -s -municode -mwindows desktools.c di.c st.c qn.c pw.c res.o -o DeskTools.exe \ + -luser32 -lgdi32 -lshell32 -ladvapi32 -lole32 -loleaut32 -loleacc -ldwmapi -static-libgcc +echo "Built DeskTools.exe" diff --git a/pw.c b/pw.c new file mode 100644 index 0000000..1081d94 --- /dev/null +++ b/pw.c @@ -0,0 +1,453 @@ +// 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 +#include "desktools.h" +#include +#include +#include + +#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); +} diff --git a/res.rc b/res.rc new file mode 100644 index 0000000..ff8490c --- /dev/null +++ b/res.rc @@ -0,0 +1,23 @@ +#include +1 ICON "app.ico" +1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "FileDescription", "DeskTools - DeskIcons, SeeThru, QuickNote and Pin a Window" + VALUE "ProductName", "DeskTools" + VALUE "FileVersion", "1.0.0" + VALUE "ProductVersion", "1.0.0" + VALUE "OriginalFilename", "DeskTools.exe" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END