From d1719b6625d1116ac4320980c0374718ab426f94 Mon Sep 17 00:00:00 2001 From: weishaupt Date: Sun, 4 Oct 2026 17:13:53 +0000 Subject: [PATCH] Add DeskTools source (main, header, DeskIcons, SeeThru, QuickNote) --- desktools.c | 261 +++++++++++++ desktools.h | 24 ++ di.c | 703 +++++++++++++++++++++++++++++++++++ qn.c | 1028 +++++++++++++++++++++++++++++++++++++++++++++++++++ st.c | 486 ++++++++++++++++++++++++ 5 files changed, 2502 insertions(+) create mode 100644 desktools.c create mode 100644 desktools.h create mode 100644 di.c create mode 100644 qn.c create mode 100644 st.c diff --git a/desktools.c b/desktools.c new file mode 100644 index 0000000..fdc6ec4 --- /dev/null +++ b/desktools.c @@ -0,0 +1,261 @@ +// DeskTools: DeskIcons, SeeThru, QuickNote and Pin a Window in one small tray program. + +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _UNICODE +#define _UNICODE +#endif +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0A00 +#include +#include +#include +#include +#include +#include "desktools.h" + +#define APP_NAME L"DeskTools" +#define WM_TRAY (WM_APP + 50) + +#define BASE_DI 10000 +#define BASE_ST 20000 +#define BASE_QN 30000 +#define BASE_PW 40000 +#define IDM_STARTUP 1 +#define IDM_HELP 2 +#define IDM_EXIT 3 + +static HINSTANCE g_inst; +static HWND g_main; +static HHOOK g_hook; +static NOTIFYICONDATAW g_nid; +static UINT g_taskbarCreated; + +/* ---------------- settings ---------------- */ + +static const WCHAR *RUN_KEY = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + +static BOOL RunEntryExists(const WCHAR *name) { + WCHAR buf[MAX_PATH * 2]; DWORD sz = sizeof(buf); + return RegGetValueW(HKEY_CURRENT_USER, RUN_KEY, name, RRF_RT_REG_SZ, NULL, buf, &sz) == ERROR_SUCCESS; +} +static void DeleteRunEntry(const WCHAR *name) { + HKEY k; + if (RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, KEY_WRITE, &k) == ERROR_SUCCESS) { + RegDeleteValueW(k, name); + RegCloseKey(k); + } +} +static void SetStartup(BOOL on) { + if (!on) { DeleteRunEntry(APP_NAME); return; } + HKEY k; + if (RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, KEY_WRITE, &k) != ERROR_SUCCESS) return; + WCHAR exe[MAX_PATH], val[MAX_PATH + 4]; + GetModuleFileNameW(NULL, exe, MAX_PATH); + swprintf(val, MAX_PATH + 4, L"\"%ls\"", exe); + RegSetValueExW(k, APP_NAME, 0, REG_SZ, (BYTE *)val, (DWORD)((wcslen(val) + 1) * sizeof(WCHAR))); + RegCloseKey(k); +} +static DWORD GetSetting(const WCHAR *name, DWORD def) { + DWORD v = def, sz = sizeof(v); + if (RegGetValueW(HKEY_CURRENT_USER, L"Software\\DeskTools", name, RRF_RT_REG_DWORD, NULL, &v, &sz) != ERROR_SUCCESS) + return def; + return v; +} +static void PutSetting(const WCHAR *name, DWORD v) { + HKEY k; + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\DeskTools", 0, NULL, 0, KEY_WRITE, NULL, &k, NULL) == ERROR_SUCCESS) { + RegSetValueExW(k, name, 0, REG_DWORD, (BYTE *)&v, sizeof(v)); + RegCloseKey(k); + } +} + +/* ---------------- tray ---------------- */ + +void DT_Balloon(const WCHAR *title, const WCHAR *text) { + if (!g_nid.hWnd) return; + g_nid.uFlags = NIF_INFO; + wcsncpy(g_nid.szInfoTitle, title, 63); g_nid.szInfoTitle[63] = 0; + wcsncpy(g_nid.szInfo, text, 255); g_nid.szInfo[255] = 0; + g_nid.dwInfoFlags = NIIF_INFO; + Shell_NotifyIconW(NIM_MODIFY, &g_nid); +} + +static void AddTrayIcon(void) { + ZeroMemory(&g_nid, sizeof(g_nid)); + g_nid.cbSize = sizeof(g_nid); + g_nid.hWnd = g_main; + g_nid.uID = 1; + g_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + g_nid.uCallbackMessage = WM_TRAY; + g_nid.hIcon = (HICON)LoadImageW(g_inst, MAKEINTRESOURCEW(1), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + wcscpy(g_nid.szTip, L"DeskTools"); + Shell_NotifyIconW(NIM_ADD, &g_nid); +} + +static void ShowHelp(void) { + MessageBoxW(NULL, + L"DESKICONS (send a window to the desktop)\n" + L" Shift+click a window's minimize button, or press Ctrl+Alt+I.\n" + L" Drag the label anywhere. Double-click it to bring the window back.\n\n" + L"SEETHRU (make a window see-through)\n" + L" Hold Ctrl+Shift and scroll over a window, down 5% at a time.\n" + L" Ctrl+Alt+Page Down / Page Up on the active window.\n" + L" Ctrl+Alt+Home: fully solid again. Ctrl+Alt+End: ghost mode\n" + L" (stays on top, clicks pass through it).\n\n" + L"QUICKNOTE (type on the desktop)\n" + L" Double-click empty wallpaper and start typing.\n" + L" Drag across text to select it, Ctrl+C to copy.\n" + L" Drag the small gray square to move a note. Right-click for color or delete.\n\n" + L"PIN A WINDOW (keep a window on top)\n" + L" Press Ctrl+Alt+P to pin or unpin the active window,\n" + L" or choose Pin a window... from the tray menu and click a window.\n" + L" A blue box with a plus sign appears on its title bar.\n" + L" Click that box to unpin it.\n\n" + L"Each tool can be turned off from its submenu in the tray menu.\n" + L"Exiting DeskTools brings every window back to normal.", + L"How to use DeskTools", MB_OK | MB_ICONINFORMATION); +} + +static void ShowTrayMenu(void) { + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"DeskTools"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + DI_AppendMenu(m, BASE_DI); + ST_AppendMenu(m, BASE_ST); + QN_AppendMenu(m, BASE_QN); + PW_AppendMenu(m, BASE_PW); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING | (RunEntryExists(APP_NAME) ? MF_CHECKED : 0), IDM_STARTUP, L"Start with Windows"); + AppendMenuW(m, MF_STRING, IDM_HELP, L"How to use"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_EXIT, L"Exit (puts every window back to normal)"); + POINT p; GetCursorPos(&p); + SetForegroundWindow(g_main); + UINT cmd = (UINT)TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_BOTTOMALIGN | TPM_RETURNCMD, p.x, p.y, 0, g_main, NULL); + PostMessageW(g_main, WM_NULL, 0, 0); + DestroyMenu(m); // also frees the submenus + + if (!cmd) return; + if (cmd >= BASE_DI && cmd < BASE_ST) { DI_Command(cmd - BASE_DI); PutSetting(L"DeskIcons", DI_IsEnabled()); } + else if (cmd >= BASE_ST && cmd < BASE_QN) { ST_Command(cmd - BASE_ST); PutSetting(L"SeeThru", ST_IsEnabled()); } + else if (cmd >= BASE_QN && cmd < BASE_PW) { QN_Command(cmd - BASE_QN); PutSetting(L"QuickNote", QN_IsEnabled()); } + else if (cmd >= BASE_PW) { PW_Command(cmd - BASE_PW); PutSetting(L"PinWindow", PW_IsEnabled()); } + else if (cmd == IDM_STARTUP) SetStartup(!RunEntryExists(APP_NAME)); + else if (cmd == IDM_HELP) ShowHelp(); + else if (cmd == IDM_EXIT) DestroyWindow(g_main); +} + +/* ---------------- one mouse hook for every tool ---------------- */ + +static LRESULT CALLBACK MouseLL(int code, WPARAM wp, LPARAM lp) { + if (code == HC_ACTION) { + MSLLHOOKSTRUCT *m = (MSLLHOOKSTRUCT *)lp; + if (PW_Mouse(wp, m)) return 1; // picking a window to pin takes priority + if (DI_Mouse(wp, m)) return 1; + if (ST_Mouse(wp, m)) return 1; + if (QN_Mouse(wp, m)) return 1; + } + return CallNextHookEx(g_hook, code, wp, lp); +} + +static LRESULT CALLBACK MainProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == g_taskbarCreated && g_taskbarCreated) { AddTrayIcon(); return 0; } + switch (msg) { + case WM_TRAY: + if (LOWORD(lp) == WM_RBUTTONUP || LOWORD(lp) == WM_LBUTTONUP) ShowTrayMenu(); + return 0; + case WM_DESTROY: + if (g_hook) { UnhookWindowsHookEx(g_hook); g_hook = NULL; } + PW_Shutdown(); + DI_Shutdown(); + ST_Shutdown(); + QN_Shutdown(); + Shell_NotifyIconW(NIM_DELETE, &g_nid); + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- start up ---------------- */ + +static BOOL OldAppRunning(const WCHAR *mutexName) { + HANDLE m = OpenMutexW(SYNCHRONIZE, FALSE, mutexName); + if (m) { CloseHandle(m); return TRUE; } + return FALSE; +} + +typedef BOOL (WINAPI *SetDpiCtxFn)(HANDLE); + +int WINAPI wWinMain(HINSTANCE inst, HINSTANCE prev, PWSTR cmd, int show) { + (void)prev; (void)cmd; (void)show; + g_inst = inst; + + HANDLE mutex = CreateMutexW(NULL, TRUE, L"DeskTools_SingleInstance_Mutex"); + if (GetLastError() == ERROR_ALREADY_EXISTS) { + MessageBoxW(NULL, L"DeskTools is already running. Look for its icon in the system tray.", APP_NAME, MB_OK | MB_ICONINFORMATION); + return 0; + } + + // The separate apps would fight DeskTools over the same shortcuts, so they need to be closed first. + WCHAR running[160] = L""; + if (OldAppRunning(L"DeskIcons_SingleInstance_Mutex")) wcscat(running, L" DeskIcons\n"); + if (OldAppRunning(L"SeeThru_SingleInstance_Mutex")) wcscat(running, L" SeeThru\n"); + if (OldAppRunning(L"QuickNote_SingleInstance_Mutex")) wcscat(running, L" QuickNote\n"); + if (running[0]) { + WCHAR msg[512]; + swprintf(msg, 512, L"DeskTools already includes these, and they are still running on their own:\n\n%ls\n" + L"Right-click each one's icon in the system tray and choose Exit, then start DeskTools again.", running); + MessageBoxW(NULL, msg, APP_NAME, MB_OK | MB_ICONINFORMATION); + ReleaseMutex(mutex); CloseHandle(mutex); + return 0; + } + + // If the separate apps were set to start with Windows, offer to switch that over to DeskTools. + BOOL oldRun = RunEntryExists(L"DeskIcons") || RunEntryExists(L"SeeThru") || RunEntryExists(L"QuickNote"); + if (oldRun && !GetSetting(L"AskedAboutOldStartup", 0)) { + PutSetting(L"AskedAboutOldStartup", 1); + if (MessageBoxW(NULL, L"Some of the separate apps (DeskIcons, SeeThru, QuickNote) are set to start with Windows.\n\n" + L"Turn those off and have DeskTools start with Windows instead?", + APP_NAME, MB_YESNO | MB_ICONQUESTION) == IDYES) { + DeleteRunEntry(L"DeskIcons"); DeleteRunEntry(L"SeeThru"); DeleteRunEntry(L"QuickNote"); + SetStartup(TRUE); + } + } + + CoInitialize(NULL); + SetDpiCtxFn setCtx = (SetDpiCtxFn)(void *)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetProcessDpiAwarenessContext"); + if (!setCtx || !setCtx((HANDLE)-4)) SetProcessDPIAware(); + + WNDCLASSEXW wc; ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); wc.lpfnWndProc = MainProc; wc.hInstance = inst; wc.lpszClassName = L"DeskToolsMain"; + wc.hIcon = LoadIconW(inst, MAKEINTRESOURCEW(1)); + RegisterClassExW(&wc); + g_main = CreateWindowExW(0, L"DeskToolsMain", APP_NAME, 0, 0, 0, 0, 0, NULL, NULL, inst, NULL); + g_taskbarCreated = RegisterWindowMessageW(L"TaskbarCreated"); + AddTrayIcon(); + + DI_Init(inst, GetSetting(L"DeskIcons", 1) != 0); + ST_Init(inst, GetSetting(L"SeeThru", 1) != 0); + QN_Init(inst, GetSetting(L"QuickNote", 1) != 0); + PW_Init(inst, GetSetting(L"PinWindow", 1) != 0); + g_hook = SetWindowsHookExW(WH_MOUSE_LL, MouseLL, inst, 0); + + if (!GetSetting(L"Welcomed", 0)) { + PutSetting(L"Welcomed", 1); + DT_Balloon(L"DeskTools is running", + L"DeskIcons, SeeThru, QuickNote and Pin a Window are all in this one icon. Right-click it for the menu and help."); + } + + MSG m; + while (GetMessageW(&m, NULL, 0, 0) > 0) { + TranslateMessage(&m); + DispatchMessageW(&m); + } + CoUninitialize(); + ReleaseMutex(mutex); CloseHandle(mutex); + return 0; +} diff --git a/desktools.h b/desktools.h new file mode 100644 index 0000000..a90b79a --- /dev/null +++ b/desktools.h @@ -0,0 +1,24 @@ +// Shared declarations for the DeskTools modules. +#ifndef DESKTOOLS_H +#define DESKTOOLS_H +#include + +void DT_Balloon(const WCHAR *title, const WCHAR *text); + +// Each module: init, shutdown, enable/disable, tray submenu, menu command, mouse hook. +#define DT_MODULE(P) \ + void P##_Init(HINSTANCE inst, BOOL enabled); \ + void P##_Shutdown(void); \ + void P##_SetEnabled(BOOL on); \ + BOOL P##_IsEnabled(void); \ + void P##_AppendMenu(HMENU parent, UINT base); \ + void P##_Command(UINT local); \ + int P##_Mouse(WPARAM wp, MSLLHOOKSTRUCT *m); + +DT_MODULE(DI) +DT_MODULE(ST) +DT_MODULE(QN) +DT_MODULE(PW) + +#define DT_ID_ENABLE 9000 +#endif diff --git a/di.c b/di.c new file mode 100644 index 0000000..eaa307d --- /dev/null +++ b/di.c @@ -0,0 +1,703 @@ +// DeskIcons: send any window to the desktop as a draggable icon. +// Shift+click a minimize button, or press Ctrl+Alt+I, to iconify the window. +// Double-click the icon to bring the window back. + +#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 +#include +#include +#include + +#define APP_NAME L"DeskIcons" +#define WM_TRAY (WM_APP + 1) +#define WM_ICONIFY (WM_APP + 2) +#define WM_RESTOREIDX (WM_APP + 3) +#define WM_CLOSEIDX (WM_APP + 4) +#define HOTKEY_ID 1 +#define MAX_ITEMS 256 +#define TIMER_CHECK 1 + +#define IDM_RESTOREALL 100 +#define IDM_ONTOP 101 +#define IDM_STARTUP 102 +#define IDM_HELP 103 +#define IDM_EXIT 104 +#define IDM_SHOWICONS 105 +#define IDM_I_RESTORE 200 +#define IDM_I_CLOSE 201 + +typedef struct { + BOOL used; + HWND target; + HWND iconWnd; + DWORD pid; + HICON hIcon; + BOOL hover; + WCHAR title[256]; +} Item; + +static Item g_items[MAX_ITEMS]; +static BOOL g_enabled = TRUE; +static HINSTANCE g_inst; +static HWND g_main; +static HHOOK g_mouseHook; +static volatile BOOL g_swallowUp; +static BOOL g_onTop; +static BOOL g_showIcons; +static BOOL g_hotkeyOk; +static NOTIFYICONDATAW g_nid; +static HFONT g_font; +static int g_dpi = 96; +static UINT g_taskbarCreated; +static WCHAR g_stateFile[MAX_PATH]; +static HWND g_lastFg; + +// drag state +static BOOL g_pressed, g_dragging; +static POINT g_dragStart, g_dragOrig; + +static int S(int v) { return MulDiv(v, g_dpi, 96); } +static int ItemW(void) { return g_showIcons ? S(88) : S(120); } +static int ItemH(void) { return g_showIcons ? S(96) : S(58); } + +/* ---------------- settings ---------------- */ + +static DWORD RegGetDword(const WCHAR *name, DWORD def) { + DWORD v = def, sz = sizeof(v); + if (RegGetValueW(HKEY_CURRENT_USER, L"Software\\DeskIcons", name, RRF_RT_REG_DWORD, NULL, &v, &sz) != ERROR_SUCCESS) + return def; + return v; +} +static void RegSetDword(const WCHAR *name, DWORD v) { + HKEY k; + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\DeskIcons", 0, NULL, 0, KEY_WRITE, NULL, &k, NULL) == ERROR_SUCCESS) { + RegSetValueExW(k, name, 0, REG_DWORD, (BYTE *)&v, sizeof(v)); + RegCloseKey(k); + } +} +static const WCHAR *RUN_KEY = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; +static BOOL StartupEnabled(void) { + WCHAR buf[MAX_PATH * 2]; DWORD sz = sizeof(buf); + return RegGetValueW(HKEY_CURRENT_USER, RUN_KEY, APP_NAME, RRF_RT_REG_SZ, NULL, buf, &sz) == ERROR_SUCCESS; +} +static void SetStartup(BOOL on) { + HKEY k; + if (RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, KEY_WRITE, &k) != ERROR_SUCCESS) return; + if (on) { + WCHAR exe[MAX_PATH], val[MAX_PATH + 4]; + GetModuleFileNameW(NULL, exe, MAX_PATH); + swprintf(val, MAX_PATH + 4, L"\"%ls\"", exe); + RegSetValueExW(k, APP_NAME, 0, REG_SZ, (BYTE *)val, (DWORD)((wcslen(val) + 1) * sizeof(WCHAR))); + } else { + RegDeleteValueW(k, APP_NAME); + } + RegCloseKey(k); +} + +/* ---------------- state file (crash recovery) ---------------- */ + +static void SaveState(void) { + FILE *f = _wfopen(g_stateFile, L"w"); + if (!f) return; + for (int i = 0; i < MAX_ITEMS; i++) { + Item *it = &g_items[i]; + if (!it->used) continue; + RECT r; GetWindowRect(it->iconWnd, &r); + fprintf(f, "%llx %lu %ld %ld\n", (unsigned long long)(ULONG_PTR)it->target, + (unsigned long)it->pid, r.left, r.top); + } + fclose(f); +} + +/* ---------------- helpers ---------------- */ + +static int CountItems(void) { + int n = 0; + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used) n++; + return n; +} + +static void UpdateTrayTip(void) { } + +static void Balloon(const WCHAR *title, const WCHAR *text) { DT_Balloon(title, text); } + +static Item *FindItemByTarget(HWND t) { + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used && g_items[i].target == t) return &g_items[i]; + return NULL; +} +static BOOL IsOurIconWindow(HWND h) { + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used && g_items[i].iconWnd == h) return TRUE; + return FALSE; +} + +static HWND FindDesktopHost(void) { + HWND p = FindWindowW(L"Progman", NULL); + if (p && FindWindowExW(p, NULL, L"SHELLDLL_DefView", NULL)) return p; + HWND w = NULL; + while ((w = FindWindowExW(NULL, w, L"WorkerW", NULL)) != NULL) + if (FindWindowExW(w, NULL, L"SHELLDLL_DefView", NULL)) return w; + return p; +} + +static BOOL IsDesktopClass(HWND h) { + WCHAR cls[64]; + if (!h || !GetClassNameW(h, cls, 64)) return FALSE; + return !wcscmp(cls, L"Progman") || !wcscmp(cls, L"WorkerW"); +} + +static BOOL CanIconify(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") || !wcscmp(cls, L"Windows.UI.Core.CoreWindow")) + return FALSE; + if (GetWindowLongW(h, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) return FALSE; + if (FindItemByTarget(h)) return FALSE; + return TRUE; +} + +static HICON GetTargetIcon(HWND h) { + int sz = S(48); + HICON hi = NULL; + DWORD pid = 0; GetWindowThreadProcessId(h, &pid); + HANDLE p = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (p) { + WCHAR path[MAX_PATH]; DWORD n = MAX_PATH; + if (QueryFullProcessImageNameW(p, 0, path, &n)) { + const WCHAR *base = wcsrchr(path, L'\\'); base = base ? base + 1 : path; + if (_wcsicmp(base, L"ApplicationFrameHost.exe") != 0) { + HICON large = NULL; + if (SHDefExtractIconW(path, 0, 0, &large, NULL, MAKELONG(sz, 16)) == S_OK && large) + hi = large; + } + } + CloseHandle(p); + } + if (!hi) { + DWORD_PTR r = 0; + if (SendMessageTimeoutW(h, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 200, &r) && r) hi = CopyIcon((HICON)r); + if (!hi) { HICON c = (HICON)GetClassLongPtrW(h, GCLP_HICON); if (c) hi = CopyIcon(c); } + if (!hi && SendMessageTimeoutW(h, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 200, &r) && r) hi = CopyIcon((HICON)r); + if (!hi) hi = CopyIcon(LoadIconW(NULL, IDI_APPLICATION)); + } + return hi; +} + +/* ---------------- rendering ---------------- */ + +static void DrawContent(HDC dc, Item *it, int W, int H) { + SelectObject(dc, g_font); + SetBkMode(dc, TRANSPARENT); + UINT fmt = DT_CENTER | DT_WORDBREAK | DT_END_ELLIPSIS | DT_EDITCONTROL | DT_NOPREFIX; + WCHAR title[256]; + wcscpy(title, it->title[0] ? it->title : L"(untitled)"); + RECT tr; + if (g_showIcons) { + int isz = S(48); + int ix = (W - isz) / 2, iy = S(6); + DrawIconEx(dc, ix, iy, it->hIcon, isz, isz, 0, NULL, DI_NORMAL); + SetRect(&tr, S(3), iy + isz + S(4), W - S(3), H - S(3)); + } else { + // text only: center the (up to 3 line) label vertically in the tile + RECT box = { S(5), S(4), W - S(5), H - S(4) }; + RECT calc = box; + DrawTextW(dc, title, -1, &calc, fmt | DT_CALCRECT); + int th = calc.bottom - calc.top, bh = box.bottom - box.top; + if (th > bh) th = bh; + tr = box; + tr.top = box.top + (bh - th) / 2; + tr.bottom = tr.top + th; + } + RECT sr = tr; OffsetRect(&sr, 1, 1); + SetTextColor(dc, RGB(0, 0, 0)); + DrawTextW(dc, title, -1, &sr, fmt); + SetTextColor(dc, RGB(255, 255, 255)); + DrawTextW(dc, title, -1, &tr, fmt); +} + +static void RenderItem(Item *it) { + int W = ItemW(), H = ItemH(); + HDC screen = GetDC(NULL); + BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi)); + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = W; bi.bmiHeader.biHeight = -H; + bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; + + BYTE *pb = NULL, *pw = NULL, *po = NULL; + HDC db = CreateCompatibleDC(screen), dw = CreateCompatibleDC(screen), dout = CreateCompatibleDC(screen); + HBITMAP bb = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&pb, NULL, 0); + HBITMAP bw = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&pw, NULL, 0); + HBITMAP bo = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&po, NULL, 0); + if (!bb || !bw || !bo) goto done; + HGDIOBJ ob = SelectObject(db, bb), ow = SelectObject(dw, bw), oo = SelectObject(dout, bo); + + RECT all = { 0, 0, W, H }; + FillRect(db, &all, (HBRUSH)GetStockObject(BLACK_BRUSH)); + FillRect(dw, &all, (HBRUSH)GetStockObject(WHITE_BRUSH)); + DrawContent(db, it, W, H); + DrawContent(dw, it, W, H); + GdiFlush(); + + // Recover per-pixel alpha by comparing the render over black and over white. + int hx0 = S(2), hy0 = S(2), hx1 = W - S(2), hy1 = H - S(2); + for (int y = 0; y < H; y++) { + for (int x = 0; x < W; x++) { + int i = (y * W + x) * 4; + int d = ((pw[i] - pb[i]) + (pw[i + 1] - pb[i + 1]) + (pw[i + 2] - pb[i + 2])) / 3; + int a = 255 - d; if (a < 0) a = 0; if (a > 255) a = 255; + int c0 = pb[i], c1 = pb[i + 1], c2 = pb[i + 2]; + if (c0 > a) c0 = a; if (c1 > a) c1 = a; if (c2 > a) c2 = a; + + if (it->hover && x >= hx0 && x < hx1 && y >= hy0 && y < hy1) { + BOOL edge = (x == hx0 || x == hx1 - 1 || y == hy0 || y == hy1 - 1); + int ha = edge ? 150 : 70; // highlight alpha + int hr = 153, hg = 209, hb = 255; // light blue, like Explorer selection + int inv = 255 - a; + c0 += (hb * ha / 255) * inv / 255; + c1 += (hg * ha / 255) * inv / 255; + c2 += (hr * ha / 255) * inv / 255; + a += ha * inv / 255; + } + if (a < 1) a = 1; // keep the whole tile clickable + po[i] = (BYTE)c0; po[i + 1] = (BYTE)c1; po[i + 2] = (BYTE)c2; po[i + 3] = (BYTE)a; + } + } + + { + SIZE sz = { W, H }; POINT src = { 0, 0 }; + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + UpdateLayeredWindow(it->iconWnd, screen, NULL, &sz, dout, &src, 0, &bf, ULW_ALPHA); + } + SelectObject(db, ob); SelectObject(dw, ow); SelectObject(dout, oo); +done: + if (bb) DeleteObject(bb); + if (bw) DeleteObject(bw); + if (bo) DeleteObject(bo); + DeleteDC(db); DeleteDC(dw); DeleteDC(dout); + ReleaseDC(NULL, screen); +} + +/* ---------------- placement and z-order ---------------- */ + +static void ApplyZOrder(HWND h) { + if (g_onTop) SetWindowPos(h, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + else { + SetWindowPos(h, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + SetWindowPos(h, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // adjusted in WM_WINDOWPOSCHANGING + } +} + +static POINT FindFreeSlot(void) { + RECT wa; SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0); + int W = ItemW(), H = ItemH(), gap = S(4), m = S(10); + int rows = (wa.bottom - wa.top - m) / (H + gap); if (rows < 1) rows = 1; + int cols = (wa.right - wa.left - m) / (W + gap); if (cols < 1) cols = 1; + for (int c = 0; c < cols; c++) { + for (int r = 0; r < rows; r++) { + RECT slot; + slot.left = wa.right - m - (c + 1) * W - c * gap; + slot.top = wa.top + m + r * (H + gap); + slot.right = slot.left + W; slot.bottom = slot.top + H; + BOOL free = TRUE; + for (int i = 0; i < MAX_ITEMS && free; i++) { + if (!g_items[i].used) continue; + RECT ir, x; GetWindowRect(g_items[i].iconWnd, &ir); + if (IntersectRect(&x, &ir, &slot)) free = FALSE; + } + if (free) { POINT p = { slot.left, slot.top }; return p; } + } + } + POINT p = { wa.right - m - W, wa.top + m }; + return p; +} + +/* ---------------- items ---------------- */ + +static Item *CreateItem(HWND target, int x, int y, BOOL useXY) { + Item *it = NULL; + for (int i = 0; i < MAX_ITEMS; i++) if (!g_items[i].used) { it = &g_items[i]; break; } + if (!it) return NULL; + ZeroMemory(it, sizeof(*it)); + it->target = target; + GetWindowThreadProcessId(target, &it->pid); + GetWindowTextW(target, it->title, 256); + it->hIcon = GetTargetIcon(target); + if (!useXY) { POINT p = FindFreeSlot(); x = p.x; y = p.y; } + it->iconWnd = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, + L"DeskIconsItem", it->title, WS_POPUP, + x, y, ItemW(), ItemH(), NULL, NULL, g_inst, it); + if (!it->iconWnd) { if (it->hIcon) DestroyIcon(it->hIcon); return NULL; } + it->used = TRUE; + RenderItem(it); + ShowWindow(it->iconWnd, SW_SHOWNOACTIVATE); + ApplyZOrder(it->iconWnd); + return it; +} + +static void RemoveItem(Item *it) { + if (!it->used) return; + it->used = FALSE; + if (it->iconWnd) DestroyWindow(it->iconWnd); + if (it->hIcon) DestroyIcon(it->hIcon); + it->iconWnd = NULL; it->hIcon = NULL; + SaveState(); + UpdateTrayTip(); +} + +static void Iconify(HWND h) { + h = GetAncestor(h, GA_ROOT); + if (!CanIconify(h)) return; + ShowWindow(h, SW_MINIMIZE); + ShowWindow(h, SW_HIDE); + if (IsWindowVisible(h)) { + Balloon(L"DeskIcons", L"That window could not be hidden. It probably belongs to a program running as administrator."); + return; + } + CreateItem(h, 0, 0, FALSE); + SaveState(); + UpdateTrayTip(); +} + +static void RestoreItem(Item *it, BOOL activate) { + if (!it->used) return; + HWND t = it->target; + if (IsWindow(t)) { + if (activate) { + ShowWindow(t, SW_SHOWMINNOACTIVE); + ShowWindow(t, SW_RESTORE); + SetForegroundWindow(t); + } else { + ShowWindow(t, SW_SHOWMINNOACTIVE); // back on the taskbar, still minimized + } + } + RemoveItem(it); +} + +static void RestoreAll(BOOL activate) { + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used) RestoreItem(&g_items[i], activate); +} + +static void RecoverFromStateFile(void) { + FILE *f = _wfopen(g_stateFile, L"r"); + if (!f) return; + unsigned long long h; unsigned long pid; long x, y; + while (fscanf(f, "%llx %lu %ld %ld", &h, &pid, &x, &y) == 4) { + HWND hw = (HWND)(ULONG_PTR)h; + DWORD realPid = 0; + if (!IsWindow(hw)) continue; + GetWindowThreadProcessId(hw, &realPid); + if (realPid != pid || IsWindowVisible(hw) || FindItemByTarget(hw)) continue; + CreateItem(hw, x, y, TRUE); + } + fclose(f); + SaveState(); +} + +/* ---------------- menus ---------------- */ + +static void ShowTrayMenu(void) { + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"DeskIcons"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING | (CountItems() ? 0 : MF_GRAYED), IDM_RESTOREALL, L"Restore all windows"); + AppendMenuW(m, MF_STRING | (g_showIcons ? MF_CHECKED : 0), IDM_SHOWICONS, L"Show program icons"); + AppendMenuW(m, MF_STRING | (g_onTop ? MF_CHECKED : 0), IDM_ONTOP, L"Keep icons on top of windows"); + AppendMenuW(m, MF_STRING | (StartupEnabled() ? MF_CHECKED : 0), IDM_STARTUP, L"Start with Windows"); + AppendMenuW(m, MF_STRING, IDM_HELP, L"How to use"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_EXIT, L"Exit (brings windows back)"); + POINT p; GetCursorPos(&p); + SetForegroundWindow(g_main); + TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_BOTTOMALIGN, p.x, p.y, 0, g_main, NULL); + PostMessageW(g_main, WM_NULL, 0, 0); + DestroyMenu(m); +} + +static void ShowHelp(void) { + MessageBoxW(NULL, + L"Send a window to the desktop:\n" + L" Hold Shift and click the window's minimize button,\n" + L" or press Ctrl+Alt+I while the window is active.\n\n" + L"The window leaves the taskbar and shows up as an icon on your desktop.\n\n" + L"Drag icons anywhere you like.\n" + L"Double-click an icon to bring its window back.\n" + L"Right-click an icon to restore or close that window.\n\n" + L"Exiting DeskIcons brings back every window it was holding.", + L"How to use DeskIcons", MB_OK | MB_ICONINFORMATION); +} + +/* ---------------- window procs ---------------- */ + +static LRESULT CALLBACK ItemProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + Item *it = (Item *)GetWindowLongPtrW(h, GWLP_USERDATA); + switch (msg) { + case WM_NCCREATE: { + CREATESTRUCTW *cs = (CREATESTRUCTW *)lp; + SetWindowLongPtrW(h, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + break; + } + case WM_MOUSEACTIVATE: + return MA_NOACTIVATE; + case WM_WINDOWPOSCHANGING: + if (!g_onTop) { + WINDOWPOS *pos = (WINDOWPOS *)lp; + if (!(pos->flags & SWP_NOZORDER)) { + HWND host = FindDesktopHost(); + if (host) { + HWND above = GetWindow(host, GW_HWNDPREV); + if (above == h) pos->flags |= SWP_NOZORDER; + else pos->hwndInsertAfter = above ? above : HWND_TOP; + } + } + } + break; + case WM_LBUTTONDOWN: { + RECT r; GetWindowRect(h, &r); + g_dragOrig.x = r.left; g_dragOrig.y = r.top; + GetCursorPos(&g_dragStart); + g_pressed = TRUE; g_dragging = FALSE; + SetCapture(h); + return 0; + } + case WM_MOUSEMOVE: { + if (it && !it->hover) { + it->hover = TRUE; + TRACKMOUSEEVENT t = { sizeof(t), TME_LEAVE, h, 0 }; + TrackMouseEvent(&t); + RenderItem(it); + } + if (g_pressed && GetCapture() == h) { + POINT p; GetCursorPos(&p); + int dx = p.x - g_dragStart.x, dy = p.y - g_dragStart.y; + if (!g_dragging && (abs(dx) > GetSystemMetrics(SM_CXDRAG) || abs(dy) > GetSystemMetrics(SM_CYDRAG))) + g_dragging = TRUE; + if (g_dragging) + SetWindowPos(h, NULL, g_dragOrig.x + dx, g_dragOrig.y + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + return 0; + } + case WM_LBUTTONUP: { + BOOL wasDrag = g_dragging; + g_pressed = FALSE; g_dragging = FALSE; + if (GetCapture() == h) ReleaseCapture(); + if (wasDrag) SaveState(); + return 0; + } + case WM_CAPTURECHANGED: + g_pressed = FALSE; g_dragging = FALSE; + return 0; + case WM_LBUTTONDBLCLK: + if (it) PostMessageW(g_main, WM_RESTOREIDX, (WPARAM)(it - g_items), 0); + return 0; + case WM_MOUSELEAVE: + if (it && it->hover) { it->hover = FALSE; RenderItem(it); } + return 0; + case WM_RBUTTONUP: { + if (!it) return 0; + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | MF_DEFAULT, IDM_I_RESTORE, L"Restore"); + AppendMenuW(m, MF_STRING, IDM_I_CLOSE, L"Close window"); + POINT p; GetCursorPos(&p); + SetForegroundWindow(g_main); + int cmd = TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_RETURNCMD, p.x, p.y, 0, g_main, NULL); + PostMessageW(g_main, WM_NULL, 0, 0); + DestroyMenu(m); + if (cmd == IDM_I_RESTORE) PostMessageW(g_main, WM_RESTOREIDX, (WPARAM)(it - g_items), 0); + else if (cmd == IDM_I_CLOSE) PostMessageW(g_main, WM_CLOSEIDX, (WPARAM)(it - g_items), 0); + return 0; + } + } + return DefWindowProcW(h, msg, wp, lp); +} + +static void AddTrayIcon(void) { } + +static LRESULT CALLBACK MainProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == g_taskbarCreated && g_taskbarCreated) { AddTrayIcon(); return 0; } + switch (msg) { + case WM_TRAY: + if (LOWORD(lp) == WM_RBUTTONUP || LOWORD(lp) == WM_LBUTTONUP) ShowTrayMenu(); + return 0; + case WM_HOTKEY: + if (wp == HOTKEY_ID) Iconify(GetForegroundWindow()); + return 0; + case WM_ICONIFY: + Iconify((HWND)wp); + return 0; + case WM_RESTOREIDX: + if (wp < MAX_ITEMS) RestoreItem(&g_items[wp], TRUE); + return 0; + case WM_CLOSEIDX: + if (wp < MAX_ITEMS && g_items[wp].used) { + HWND t = g_items[wp].target; + RestoreItem(&g_items[wp], TRUE); + if (IsWindow(t)) PostMessageW(t, WM_SYSCOMMAND, SC_CLOSE, 0); + } + return 0; + case WM_TIMER: + if (wp == TIMER_CHECK) { + for (int i = 0; i < MAX_ITEMS; i++) { + Item *it = &g_items[i]; + if (!it->used) continue; + if (!IsWindow(it->target) || IsWindowVisible(it->target)) { RemoveItem(it); continue; } + WCHAR t[256]; GetWindowTextW(it->target, t, 256); + if (wcscmp(t, it->title)) { wcscpy(it->title, t); RenderItem(it); } + if (IsIconic(it->iconWnd) || !IsWindowVisible(it->iconWnd)) ShowWindow(it->iconWnd, SW_SHOWNOACTIVATE); + } + // When the desktop gets focus (Win+D, clicking the wallpaper), make sure icons sit above it. + HWND fg = GetForegroundWindow(); + if (fg != g_lastFg) { + g_lastFg = fg; + if (IsDesktopClass(fg)) + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used) ApplyZOrder(g_items[i].iconWnd); + } + } + return 0; + case WM_COMMAND: + switch (LOWORD(wp)) { + case IDM_RESTOREALL: RestoreAll(FALSE); break; + case IDM_ONTOP: + g_onTop = !g_onTop; RegSetDword(L"OnTop", g_onTop); + for (int i = 0; i < MAX_ITEMS; i++) if (g_items[i].used) ApplyZOrder(g_items[i].iconWnd); + break; + case IDM_SHOWICONS: + g_showIcons = !g_showIcons; RegSetDword(L"ShowIcons", g_showIcons); + for (int i = 0; i < MAX_ITEMS; i++) { + if (!g_items[i].used) continue; + SetWindowPos(g_items[i].iconWnd, NULL, 0, 0, ItemW(), ItemH(), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + RenderItem(&g_items[i]); + } + SaveState(); + break; + case IDM_STARTUP: SetStartup(!StartupEnabled()); break; + case IDM_HELP: ShowHelp(); break; + case IDM_EXIT: DestroyWindow(h); break; + } + return 0; + case WM_QUERYENDSESSION: + RestoreAll(FALSE); + return TRUE; + case WM_DESTROY: + RestoreAll(FALSE); + DeleteFileW(g_stateFile); + KillTimer(h, TIMER_CHECK); + if (g_hotkeyOk) UnregisterHotKey(h, HOTKEY_ID); + if (g_mouseHook) UnhookWindowsHookEx(g_mouseHook); + Shell_NotifyIconW(NIM_DELETE, &g_nid); + + return 0; + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- shift+click on minimize button ---------------- */ + +int DI_Mouse(WPARAM wp, MSLLHOOKSTRUCT *m) { + if (!g_enabled) { g_swallowUp = FALSE; return 0; } + if (wp == WM_LBUTTONDOWN && (GetAsyncKeyState(VK_SHIFT) & 0x8000) && !(m->flags & LLMHF_INJECTED)) { + HWND hit = WindowFromPoint(m->pt); + if (hit) { + HWND root = GetAncestor(hit, GA_ROOT); + LPARAM pt = MAKELPARAM((WORD)(SHORT)m->pt.x, (WORD)(SHORT)m->pt.y); + DWORD_PTR r = 0; + BOOL isMin = FALSE; + if (SendMessageTimeoutW(hit, WM_NCHITTEST, 0, pt, SMTO_ABORTIFHUNG, 150, &r) && r == HTMINBUTTON) isMin = TRUE; + else if (root && root != hit && + SendMessageTimeoutW(root, WM_NCHITTEST, 0, pt, SMTO_ABORTIFHUNG, 150, &r) && r == HTMINBUTTON) isMin = TRUE; + if (isMin && root && !IsOurIconWindow(root)) { + g_swallowUp = TRUE; + PostMessageW(g_main, WM_ICONIFY, (WPARAM)root, 0); + return 1; + } + } + } else if (wp == WM_LBUTTONUP && g_swallowUp) { + g_swallowUp = FALSE; + return 1; + } + return 0; +} + +/* ---------------- entry ---------------- */ + +/* ---------------- DeskTools module interface ---------------- */ + +void DI_SetEnabled(BOOL on) { + g_enabled = on; + if (on) { + if (!g_hotkeyOk) g_hotkeyOk = RegisterHotKey(g_main, HOTKEY_ID, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 'I'); + } else { + RestoreAll(FALSE); + if (g_hotkeyOk) { UnregisterHotKey(g_main, HOTKEY_ID); g_hotkeyOk = FALSE; } + } +} +BOOL DI_IsEnabled(void) { return g_enabled; } + +void DI_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_font = CreateFontW(-MulDiv(9, g_dpi, 72), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, L"Segoe UI"); + WCHAR dir[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, dir))) { + wcscat(dir, L"\\DeskIcons"); + CreateDirectoryW(dir, NULL); + swprintf(g_stateFile, MAX_PATH, L"%ls\\state.txt", dir); + } else { + GetTempPathW(MAX_PATH, dir); + swprintf(g_stateFile, MAX_PATH, L"%lsDeskIcons_state.txt", dir); + } + g_onTop = RegGetDword(L"OnTop", 0) != 0; + g_showIcons = RegGetDword(L"ShowIcons", 0) != 0; + + WNDCLASSEXW wc; ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); wc.lpfnWndProc = MainProc; wc.hInstance = inst; wc.lpszClassName = L"DeskIconsMain"; + RegisterClassExW(&wc); + WNDCLASSEXW ic; ZeroMemory(&ic, sizeof(ic)); + ic.cbSize = sizeof(ic); ic.style = CS_DBLCLKS; ic.lpfnWndProc = ItemProc; ic.hInstance = inst; + ic.hCursor = LoadCursorW(NULL, IDC_ARROW); ic.lpszClassName = L"DeskIconsItem"; + RegisterClassExW(&ic); + + g_main = CreateWindowExW(0, L"DeskIconsMain", APP_NAME, 0, 0, 0, 0, 0, NULL, NULL, inst, NULL); + SetTimer(g_main, TIMER_CHECK, 1000, NULL); + RecoverFromStateFile(); + g_enabled = !enabled; + DI_SetEnabled(enabled); +} + +void DI_Shutdown(void) { + if (g_main) { DestroyWindow(g_main); g_main = NULL; } + if (g_font) { DeleteObject(g_font); g_font = NULL; } +} + +void DI_AppendMenu(HMENU parent, UINT base) { + HMENU m = CreatePopupMenu(); + UINT dis = g_enabled ? 0 : MF_GRAYED; + int n = CountItems(); + 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 | ((n && g_enabled) ? 0 : MF_GRAYED), base + IDM_RESTOREALL, L"Restore all windows"); + AppendMenuW(m, MF_STRING | dis | (g_showIcons ? MF_CHECKED : 0), base + IDM_SHOWICONS, L"Show program icons"); + AppendMenuW(m, MF_STRING | dis | (g_onTop ? MF_CHECKED : 0), base + IDM_ONTOP, L"Keep labels on top of windows"); + WCHAR label[64]; + if (n) swprintf(label, 64, L"DeskIcons (%d on desktop)", n); else wcscpy(label, L"DeskIcons"); + AppendMenuW(parent, MF_POPUP, (UINT_PTR)m, label); +} + +void DI_Command(UINT local) { + if (local == DT_ID_ENABLE) { DI_SetEnabled(!g_enabled); return; } + SendMessageW(g_main, WM_COMMAND, local, 0); +} diff --git a/qn.c b/qn.c new file mode 100644 index 0000000..0d969a4 --- /dev/null +++ b/qn.c @@ -0,0 +1,1028 @@ +// QuickNote: type directly on the Windows desktop. +// Double-click an empty spot on the wallpaper and start typing. The text stays +// on the desktop (behind your windows, like desktop icons) and is saved automatically. +// Drag across text to select it, Ctrl+C to copy. Drag the grip on the left edge to move a note. + +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _UNICODE +#define _UNICODE +#endif +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0A00 +#define COBJMACROS +#include +#include "desktools.h" +#include +#include +#include +#include +#include +#include + +#define APP_NAME L"QuickNote" +#define WM_TRAY (WM_APP + 1) +#define WM_DESKDBL (WM_APP + 2) +#define WM_KILLNOTE (WM_APP + 3) +#define MAX_NOTES 256 +#define MAX_LINES 2000 +#define FONT_PT 10 + +#define TIMER_BLINK 1 +#define TIMER_SAVE 2 +#define TIMER_CHECK 3 + +#define IDM_NEW 100 +#define IDM_HIDE 101 +#define IDM_STARTUP 102 +#define IDM_HELP 103 +#define IDM_EXIT 104 +#define IDM_COLOR0 200 +#define IDM_COPY 400 +#define IDM_COPYALL 401 +#define IDM_PASTE 402 +#define IDM_SELALL 403 +#define IDM_DELETE 404 + +typedef struct { + BOOL used; + HWND hwnd; + WCHAR *text; + int len, cap; + int caret; // cursor position + int anchor; // other end of the selection (== caret when nothing is selected) + COLORREF color; + BOOL active, caretOn, hover; +} Note; + +static const COLORREF COLORS[] = { + RGB(0, 0, 0), RGB(255, 255, 255), RGB(255, 214, 10), RGB(255, 140, 40), + RGB(240, 70, 70), RGB(70, 200, 110), RGB(80, 160, 255), RGB(235, 110, 200) +}; +static const WCHAR *COLOR_NAMES[] = { + L"Black", L"White", L"Yellow", L"Orange", L"Red", L"Green", L"Blue", L"Pink" +}; +#define NCOLORS 8 + +static Note g_notes[MAX_NOTES]; +static BOOL g_enabled = TRUE; +static HINSTANCE g_inst; +static HWND g_main; +static HHOOK g_hook; +static NOTIFYICONDATAW g_nid; +static UINT g_taskbarCreated; +static int g_dpi = 96; +static HFONT g_font; +static WCHAR g_file[MAX_PATH]; +static BOOL g_dirty, g_hidden; +static HWND g_lastFg; +static int g_lastColor = 0; + +// mouse state inside a note +static BOOL g_moving, g_selecting; +static POINT g_dragStart, g_dragOrig; + +// double-click detection in the hook +static DWORD g_lastDownTime; +static POINT g_lastDownPt; + +static int S(int v) { return MulDiv(v, g_dpi, 96); } +static int GripW(void) { return S(8); } // grip square size + +/* ---------------- settings ---------------- */ + +static const WCHAR *RUN_KEY = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; +static BOOL StartupEnabled(void) { + WCHAR buf[MAX_PATH * 2]; DWORD sz = sizeof(buf); + return RegGetValueW(HKEY_CURRENT_USER, RUN_KEY, APP_NAME, RRF_RT_REG_SZ, NULL, buf, &sz) == ERROR_SUCCESS; +} +static void SetStartup(BOOL on) { + HKEY k; + if (RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, KEY_WRITE, &k) != ERROR_SUCCESS) return; + if (on) { + WCHAR exe[MAX_PATH], val[MAX_PATH + 4]; + GetModuleFileNameW(NULL, exe, MAX_PATH); + swprintf(val, MAX_PATH + 4, L"\"%ls\"", exe); + RegSetValueExW(k, APP_NAME, 0, REG_SZ, (BYTE *)val, (DWORD)((wcslen(val) + 1) * sizeof(WCHAR))); + } else { + RegDeleteValueW(k, APP_NAME); + } + RegCloseKey(k); +} +static DWORD RegGetFlag(const WCHAR *name) { + DWORD v = 0, sz = sizeof(v); + RegGetValueW(HKEY_CURRENT_USER, L"Software\\QuickNote", name, RRF_RT_REG_DWORD, NULL, &v, &sz); + return v; +} +static void RegSetFlag(const WCHAR *name, DWORD v) { + HKEY k; + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\QuickNote", 0, NULL, 0, KEY_WRITE, NULL, &k, NULL) == ERROR_SUCCESS) { + RegSetValueExW(k, name, 0, REG_DWORD, (BYTE *)&v, sizeof(v)); + RegCloseKey(k); + } +} + +/* ---------------- text buffer ---------------- */ + +static void EnsureCap(Note *n, int need) { + if (n->cap >= need + 1) return; + int c = n->cap ? n->cap : 64; + while (c < need + 1) c *= 2; + n->text = (WCHAR *)realloc(n->text, c * sizeof(WCHAR)); + n->cap = c; +} +static BOOL HasSel(Note *n) { return n->anchor != n->caret; } +static void SelRange(Note *n, int *a, int *b) { + *a = n->anchor < n->caret ? n->anchor : n->caret; + *b = n->anchor < n->caret ? n->caret : n->anchor; +} +static void DeleteRange(Note *n, int a, int b) { + if (a < 0) a = 0; + if (b > n->len) b = n->len; + if (b <= a) return; + memmove(n->text + a, n->text + b, (n->len - b) * sizeof(WCHAR)); + n->len -= (b - a); + n->text[n->len] = 0; + n->caret = n->anchor = a; +} +static void DeleteSel(Note *n) { + if (!HasSel(n)) return; + int a, b; SelRange(n, &a, &b); + DeleteRange(n, a, b); +} +static void InsertText(Note *n, const WCHAR *s, int cnt) { + DeleteSel(n); + EnsureCap(n, n->len + cnt); + memmove(n->text + n->caret + cnt, n->text + n->caret, (n->len - n->caret) * sizeof(WCHAR)); + memcpy(n->text + n->caret, s, cnt * sizeof(WCHAR)); + n->len += cnt; n->caret += cnt; + n->anchor = n->caret; + n->text[n->len] = 0; +} + +static int LineStarts(Note *n, int *starts) { + int c = 0; + starts[c++] = 0; + for (int i = 0; i < n->len && c < MAX_LINES; i++) if (n->text[i] == L'\n') starts[c++] = i + 1; + return c; +} +static int LineEnd(Note *n, int *starts, int count, int line) { + return (line + 1 < count) ? starts[line + 1] - 1 : n->len; +} +static int LineOfIndex(int *starts, int count, int idx) { + int l = 0; + while (l + 1 < count && starts[l + 1] <= idx) l++; + return l; +} +static BOOL IsWordChar(WCHAR c) { return c != L' ' && c != L'\t' && c != L'\n'; } + +/* ---------------- layout ---------------- */ + +typedef struct { int left, pad, lineH, w, h, lines; } Layout; + +static void NoteLayout(Note *n, HDC dc, int *starts, Layout *L) { + TEXTMETRICW tm; GetTextMetricsW(dc, &tm); + L->pad = S(4); + L->left = GripW() + S(3); + L->lineH = tm.tmHeight; + L->lines = LineStarts(n, starts); + int maxw = 0; + for (int i = 0; i < L->lines; i++) { + int a = starts[i], b = LineEnd(n, starts, L->lines, i); + SIZE sz = { 0, 0 }; + if (b > a) GetTextExtentPoint32W(dc, n->text + a, b - a, &sz); + if (sz.cx > maxw) maxw = sz.cx; + } + if (maxw < S(20)) maxw = S(20); + L->w = L->left + maxw + L->pad + S(4); + L->h = L->lines * L->lineH + L->pad * 2; +} + +static int TextX(HDC dc, Note *n, int lineStart, int idx) { + SIZE sz = { 0, 0 }; + if (idx > lineStart) GetTextExtentPoint32W(dc, n->text + lineStart, idx - lineStart, &sz); + return sz.cx; +} + +static int CaretFromPoint(Note *n, int x, int y) { + HDC dc = CreateCompatibleDC(NULL); + SelectObject(dc, g_font); + static int starts[MAX_LINES]; + Layout L; NoteLayout(n, dc, starts, &L); + int line = (y < L.pad) ? 0 : (y - L.pad) / L.lineH; + if (line >= L.lines) line = L.lines - 1; + int a = starts[line], b = LineEnd(n, starts, L.lines, line), cnt = b - a; + int lx = x - L.left, result = a; + if (y < 0) result = 0; // dragged above the note: start of text + else if (y >= L.h) result = n->len; // dragged below: end of text + else if (cnt > 0 && lx > 0) { + int *dx = (int *)malloc(cnt * sizeof(int)); + SIZE sz; + GetTextExtentExPointW(dc, n->text + a, cnt, 0, NULL, dx, &sz); + result = b; + for (int i = 0; i < cnt; i++) { + int left = i ? dx[i - 1] : 0, right = dx[i]; + if (lx < (left + right) / 2) { result = a + i; break; } + } + free(dx); + } + DeleteDC(dc); + return result; +} + +/* ---------------- desktop z-order ---------------- */ + +static HWND FindDesktopHost(void) { + HWND p = FindWindowW(L"Progman", NULL); + if (p && FindWindowExW(p, NULL, L"SHELLDLL_DefView", NULL)) return p; + HWND w = NULL; + while ((w = FindWindowExW(NULL, w, L"WorkerW", NULL)) != NULL) + if (FindWindowExW(w, NULL, L"SHELLDLL_DefView", NULL)) return w; + return p; +} + +static void PushToDesktop(HWND h) { + SetWindowPos(h, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // adjusted in WM_WINDOWPOSCHANGING +} + +static BOOL IsDesktopClass(HWND h) { + WCHAR cls[64]; + if (!h || !GetClassNameW(h, cls, 64)) return FALSE; + return !wcscmp(cls, L"Progman") || !wcscmp(cls, L"WorkerW"); +} + +/* ---------------- rendering ---------------- */ + +static void DrawTextLayer(HDC dc, Note *n, int *starts, Layout *L) { + SetBkMode(dc, TRANSPARENT); + SelectObject(dc, g_font); + int lum = (GetRValue(n->color) * 3 + GetGValue(n->color) * 6 + GetBValue(n->color)) / 10; + for (int pass = (lum > 128 ? 0 : 1); pass < 2; pass++) { // light text gets a dark shadow; dark text none + SetTextColor(dc, pass == 0 ? RGB(0, 0, 0) : n->color); + int off = pass == 0 ? 1 : 0; + for (int i = 0; i < L->lines; i++) { + int a = starts[i], b = LineEnd(n, starts, L->lines, i); + if (b <= a) continue; + TextOutW(dc, L->left + off, L->pad + i * L->lineH + off, n->text + a, b - a); + } + } +} + +static void RenderNote(Note *n) { + if (!n->used || !n->hwnd) return; + static int starts[MAX_LINES]; + HDC screen = GetDC(NULL); + HDC mdc = CreateCompatibleDC(screen); + SelectObject(mdc, g_font); + Layout L; NoteLayout(n, mdc, starts, &L); + int W = L.w, H = L.h; + + // caret position + int cline = LineOfIndex(starts, L.lines, n->caret); + int caretX = L.left + TextX(mdc, n, starts[cline], n->caret), caretY = L.pad + cline * L.lineH; + + // selection: per line pixel span [x1, x2) + int *selX1 = NULL, *selX2 = NULL, sa = 0, sb = 0, la = 0, lb = -1; + if (HasSel(n)) { + SelRange(n, &sa, &sb); + la = LineOfIndex(starts, L.lines, sa); + lb = LineOfIndex(starts, L.lines, sb); + selX1 = (int *)calloc(L.lines, sizeof(int)); + selX2 = (int *)calloc(L.lines, sizeof(int)); + for (int i = la; i <= lb; i++) { + int ls = starts[i], le = LineEnd(n, starts, L.lines, i); + selX1[i] = L.left + (i == la ? TextX(mdc, n, ls, sa) : 0); + selX2[i] = L.left + (i == lb ? TextX(mdc, n, ls, sb) : TextX(mdc, n, ls, le) + S(4)); + } + } + DeleteDC(mdc); + + BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi)); + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = W; bi.bmiHeader.biHeight = -H; + bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; + BYTE *pb = NULL, *pw = NULL, *po = NULL; + HDC db = CreateCompatibleDC(screen), dw = CreateCompatibleDC(screen), dout = CreateCompatibleDC(screen); + HBITMAP bb = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&pb, NULL, 0); + HBITMAP bw = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&pw, NULL, 0); + HBITMAP bo = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, (void **)&po, NULL, 0); + if (bb && bw && bo) { + HGDIOBJ ob = SelectObject(db, bb), ow = SelectObject(dw, bw), oo = SelectObject(dout, bo); + RECT all = { 0, 0, W, H }; + FillRect(db, &all, (HBRUSH)GetStockObject(BLACK_BRUSH)); + FillRect(dw, &all, (HBRUSH)GetStockObject(WHITE_BRUSH)); + DrawTextLayer(db, n, starts, &L); + DrawTextLayer(dw, n, starts, &L); + GdiFlush(); + + // the only extra mark is a small grip square, shown while pointing at or editing the note + BOOL showGrip = n->active || n->hover; + int gw = GripW(), gy = L.pad + (L.lineH - gw) / 2; + int cw = S(1) > 1 ? S(1) : 1; + BYTE tr = GetRValue(n->color), tg = GetGValue(n->color), tb = GetBValue(n->color); + + for (int y = 0; y < H; y++) { + int line = (y >= L.pad) ? (y - L.pad) / L.lineH : -1; + BOOL lineSel = selX1 && line >= la && line <= lb && line < L.lines; + for (int x = 0; x < W; x++) { + int i = (y * W + x) * 4; + int d = ((pw[i] - pb[i]) + (pw[i + 1] - pb[i + 1]) + (pw[i + 2] - pb[i + 2])) / 3; + int a = 255 - d; if (a < 0) a = 0; if (a > 255) a = 255; + int c0 = pb[i], c1 = pb[i + 1], c2 = pb[i + 2]; + if (c0 > a) c0 = a; if (c1 > a) c1 = a; if (c2 > a) c2 = a; + + // layers under the text: selection highlight and the grip square + int ua = 0, ur = 0, ug = 0, ub = 0; + if (lineSel && x >= selX1[line] && x < selX2[line]) { ur = 51; ug = 153; ub = 255; ua = 130; } + else if (showGrip && x < gw && y >= gy && y < gy + gw) { ur = ug = ub = 120; ua = 210; } + if (ua) { + int inv = 255 - a; + c0 += (ub * ua / 255) * inv / 255; + c1 += (ug * ua / 255) * inv / 255; + c2 += (ur * ua / 255) * inv / 255; + a += ua * inv / 255; + } + if (n->active && n->caretOn && !HasSel(n) && x >= caretX && x < caretX + cw && + y >= caretY && y < caretY + L.lineH) { + c0 = tb; c1 = tg; c2 = tr; a = 255; + } + if (a < 1) a = 1; // whole box stays clickable + po[i] = (BYTE)c0; po[i + 1] = (BYTE)c1; po[i + 2] = (BYTE)c2; po[i + 3] = (BYTE)a; + } + } + SIZE sz = { W, H }; POINT src = { 0, 0 }; + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + UpdateLayeredWindow(n->hwnd, screen, NULL, &sz, dout, &src, 0, &bf, ULW_ALPHA); + SelectObject(db, ob); SelectObject(dw, ow); SelectObject(dout, oo); + } + free(selX1); free(selX2); + if (bb) DeleteObject(bb); + if (bw) DeleteObject(bw); + if (bo) DeleteObject(bo); + DeleteDC(db); DeleteDC(dw); DeleteDC(dout); + ReleaseDC(NULL, screen); +} + +/* ---------------- saving ---------------- */ + +static void MarkDirty(void) { g_dirty = TRUE; SetTimer(g_main, TIMER_SAVE, 1200, NULL); } + +static void SaveNotes(void) { + g_dirty = FALSE; + size_t cap = 4096, len = 0; + WCHAR *buf = (WCHAR *)malloc(cap * sizeof(WCHAR)); + for (int k = 0; k < MAX_NOTES; k++) { + Note *n = &g_notes[k]; + if (!n->used || n->len == 0) continue; + RECT r; GetWindowRect(n->hwnd, &r); + WCHAR head[96]; + int hl = swprintf(head, 96, L"%ld\t%ld\t%d\t%06lX\t", r.left, r.top, FONT_PT, (unsigned long)n->color); + size_t need = len + hl + n->len * 2 + 4; + if (need > cap) { while (need > cap) cap *= 2; buf = (WCHAR *)realloc(buf, cap * sizeof(WCHAR)); } + memcpy(buf + len, head, hl * sizeof(WCHAR)); len += hl; + for (int i = 0; i < n->len; i++) { + WCHAR c = n->text[i]; + if (c == L'\\') { buf[len++] = L'\\'; buf[len++] = L'\\'; } + else if (c == L'\n') { buf[len++] = L'\\'; buf[len++] = L'n'; } + else if (c == L'\t') { buf[len++] = L'\\'; buf[len++] = L't'; } + else if (c == L'\r') { } + else buf[len++] = c; + } + buf[len++] = L'\n'; + } + int bytes = WideCharToMultiByte(CP_UTF8, 0, buf, (int)len, NULL, 0, NULL, NULL); + char *utf8 = (char *)malloc(bytes + 1); + WideCharToMultiByte(CP_UTF8, 0, buf, (int)len, utf8, bytes, NULL, NULL); + WCHAR tmp[MAX_PATH + 8]; swprintf(tmp, MAX_PATH + 8, L"%ls.tmp", g_file); + HANDLE f = CreateFileW(tmp, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (f != INVALID_HANDLE_VALUE) { + DWORD wr; WriteFile(f, utf8, bytes, &wr, NULL); + CloseHandle(f); + MoveFileExW(tmp, g_file, MOVEFILE_REPLACE_EXISTING); + } + free(utf8); free(buf); +} + +/* ---------------- notes ---------------- */ + +static Note *NewNoteSlot(void) { + for (int i = 0; i < MAX_NOTES; i++) if (!g_notes[i].used) { + ZeroMemory(&g_notes[i], sizeof(Note)); + return &g_notes[i]; + } + return NULL; +} + +static void ForceForeground(HWND h) { + HWND fg = GetForegroundWindow(); + DWORD ft = fg ? GetWindowThreadProcessId(fg, NULL) : 0, me = GetCurrentThreadId(); + if (ft && ft != me) AttachThreadInput(me, ft, TRUE); + BringWindowToTop(h); + SetForegroundWindow(h); + SetFocus(h); + if (ft && ft != me) AttachThreadInput(me, ft, FALSE); +} + +static Note *CreateNote(int x, int y, const WCHAR *text, int tlen, COLORREF color, BOOL activate) { + Note *n = NewNoteSlot(); + if (!n) return NULL; + n->used = TRUE; + n->color = color; + EnsureCap(n, tlen); + if (tlen) memcpy(n->text, text, tlen * sizeof(WCHAR)); + n->len = tlen; n->text[n->len] = 0; n->caret = n->anchor = tlen; + n->active = activate; + n->caretOn = TRUE; + n->hwnd = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW, L"QuickNoteText", L"QuickNote", WS_POPUP, + x, y, 10, 10, NULL, NULL, g_inst, n); + if (!n->hwnd) { free(n->text); n->used = FALSE; return NULL; } + RenderNote(n); + if (g_hidden) return n; + if (activate) { + ShowWindow(n->hwnd, SW_SHOW); + ForceForeground(n->hwnd); + } else { + ShowWindow(n->hwnd, SW_SHOWNA); + PushToDesktop(n->hwnd); + } + return n; +} + +static void DeleteNote(Note *n) { + if (!n->used) return; + n->used = FALSE; + if (n->hwnd) DestroyWindow(n->hwnd); + n->hwnd = NULL; + free(n->text); n->text = NULL; n->len = n->cap = 0; + MarkDirty(); +} + +static void LoadNotes(void) { + HANDLE f = CreateFileW(g_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (f == INVALID_HANDLE_VALUE) return; + DWORD size = GetFileSize(f, NULL), rd = 0; + char *utf8 = (char *)malloc(size + 1); + ReadFile(f, utf8, size, &rd, NULL); + CloseHandle(f); + int wl = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)rd, NULL, 0); + WCHAR *w = (WCHAR *)malloc((wl + 1) * sizeof(WCHAR)); + MultiByteToWideChar(CP_UTF8, 0, utf8, (int)rd, w, wl); + w[wl] = 0; + free(utf8); + + WCHAR *line = w; + while (line && *line) { + WCHAR *nl = wcschr(line, L'\n'); + if (nl) *nl = 0; + long x, y; int sz; unsigned long col; int consumed = 0; + if (swscanf(line, L"%ld\t%ld\t%d\t%lx\t%n", &x, &y, &sz, &col, &consumed) >= 4 && consumed > 0) { + WCHAR *src = line + consumed; + int sl = (int)wcslen(src); + WCHAR *txt = (WCHAR *)malloc((sl + 1) * sizeof(WCHAR)); + int tl = 0; + for (int i = 0; i < sl; i++) { + if (src[i] == L'\\' && i + 1 < sl) { + WCHAR e = src[++i]; + txt[tl++] = e == L'n' ? L'\n' : e == L't' ? L'\t' : e; + } else if (src[i] != L'\r') txt[tl++] = src[i]; + } + POINT p = { x + 5, y + 5 }; + if (!MonitorFromPoint(p, MONITOR_DEFAULTTONULL)) { + RECT wa; SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0); + x = wa.left + S(40); y = wa.top + S(40); + } + if (tl > 0) CreateNote((int)x, (int)y, txt, tl, (COLORREF)col, FALSE); + free(txt); + } + line = nl ? nl + 1 : NULL; + } + free(w); +} + +static void SetAllHidden(BOOL hide) { + g_hidden = hide; + for (int i = 0; i < MAX_NOTES; i++) { + Note *n = &g_notes[i]; + if (!n->used) continue; + if (hide) ShowWindow(n->hwnd, SW_HIDE); + else { ShowWindow(n->hwnd, SW_SHOWNA); PushToDesktop(n->hwnd); } + } +} + +/* ---------------- clipboard ---------------- */ + +static void CopyToClipboard(HWND h, const WCHAR *s, int len) { + if (len <= 0 || !OpenClipboard(h)) return; + EmptyClipboard(); + int extra = 0; + for (int i = 0; i < len; i++) if (s[i] == L'\n') extra++; + HGLOBAL g = GlobalAlloc(GMEM_MOVEABLE, (len + extra + 1) * sizeof(WCHAR)); + if (g) { + WCHAR *p = (WCHAR *)GlobalLock(g); int k = 0; + for (int i = 0; i < len; i++) { if (s[i] == L'\n') p[k++] = L'\r'; p[k++] = s[i]; } + p[k] = 0; + GlobalUnlock(g); + SetClipboardData(CF_UNICODETEXT, g); + } + CloseClipboard(); +} + +// copies the selection, or the whole note when nothing is selected +static void CopyNote(HWND h, Note *n, BOOL whole) { + if (!whole && HasSel(n)) { + int a, b; SelRange(n, &a, &b); + CopyToClipboard(h, n->text + a, b - a); + } else { + CopyToClipboard(h, n->text, n->len); + } +} + +static void PasteFromClipboard(HWND h, Note *n) { + if (!OpenClipboard(h)) return; + HANDLE g = GetClipboardData(CF_UNICODETEXT); + if (g) { + const WCHAR *p = (const WCHAR *)GlobalLock(g); + if (p) { + int l = (int)wcslen(p); + WCHAR *clean = (WCHAR *)malloc((l + 1) * sizeof(WCHAR)); int k = 0; + for (int i = 0; i < l; i++) if (p[i] != L'\r') clean[k++] = p[i]; + InsertText(n, clean, k); + free(clean); + GlobalUnlock(g); + } + } + CloseClipboard(); +} + +/* ---------------- note window ---------------- */ + +static void MoveCaretVertical(Note *n, int dir) { + static int starts[MAX_LINES]; + HDC dc = CreateCompatibleDC(NULL); + SelectObject(dc, g_font); + Layout L; NoteLayout(n, dc, starts, &L); + int line = LineOfIndex(starts, L.lines, n->caret); + int x = L.left + TextX(dc, n, starts[line], n->caret); + DeleteDC(dc); + int target = line + dir; + if (target < 0) { n->caret = 0; return; } + if (target >= L.lines) { n->caret = n->len; return; } + n->caret = CaretFromPoint(n, x, L.pad + target * L.lineH + L.lineH / 2); +} + +static void SelectWordAt(Note *n, int idx) { + int a = idx, b = idx; + if (a < n->len && !IsWordChar(n->text[a]) && a > 0 && IsWordChar(n->text[a - 1])) { a--; b--; } + while (a > 0 && IsWordChar(n->text[a - 1])) a--; + while (b < n->len && IsWordChar(n->text[b])) b++; + n->anchor = a; n->caret = b; +} + +static void ShowNoteMenu(HWND h, Note *n) { + HMENU m = CreatePopupMenu(), mc = CreatePopupMenu(); + for (int i = 0; i < NCOLORS; i++) + AppendMenuW(mc, MF_STRING | (n->color == COLORS[i] ? MF_CHECKED : 0), IDM_COLOR0 + i, COLOR_NAMES[i]); + AppendMenuW(m, MF_STRING | (HasSel(n) ? 0 : MF_GRAYED), IDM_COPY, L"Copy\tCtrl+C"); + AppendMenuW(m, MF_STRING, IDM_COPYALL, L"Copy all text"); + AppendMenuW(m, MF_STRING | (IsClipboardFormatAvailable(CF_UNICODETEXT) ? 0 : MF_GRAYED), IDM_PASTE, L"Paste\tCtrl+V"); + AppendMenuW(m, MF_STRING, IDM_SELALL, L"Select all\tCtrl+A"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_POPUP, (UINT_PTR)mc, L"Color"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_DELETE, L"Delete this note"); + POINT p; GetCursorPos(&p); + int cmd = TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_RETURNCMD, p.x, p.y, 0, h, NULL); + DestroyMenu(m); + if (cmd >= IDM_COLOR0 && cmd < IDM_COLOR0 + NCOLORS) { + n->color = COLORS[cmd - IDM_COLOR0]; g_lastColor = cmd - IDM_COLOR0; + RegSetFlag(L"LastColor", g_lastColor); + RenderNote(n); MarkDirty(); + } else if (cmd == IDM_COPY) { + CopyNote(h, n, FALSE); + } else if (cmd == IDM_COPYALL) { + CopyNote(h, n, TRUE); + } else if (cmd == IDM_PASTE) { + PasteFromClipboard(h, n); RenderNote(n); MarkDirty(); + } else if (cmd == IDM_SELALL) { + n->anchor = 0; n->caret = n->len; RenderNote(n); + } else if (cmd == IDM_DELETE) { + PostMessageW(g_main, WM_KILLNOTE, (WPARAM)(n - g_notes), 0); + } +} + +static BOOL InGrip(Note *n, int x, int y) { + // the grip square sits beside the first line of text + HDC dc = CreateCompatibleDC(NULL); + SelectObject(dc, g_font); + TEXTMETRICW tm; GetTextMetricsW(dc, &tm); + DeleteDC(dc); + int g = GripW(), gy = S(4) + (tm.tmHeight - g) / 2; + (void)n; + return x >= 0 && x < g + S(2) && y >= gy - S(2) && y < gy + g + S(2); +} + +static LRESULT CALLBACK NoteProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + Note *n = (Note *)GetWindowLongPtrW(h, GWLP_USERDATA); + switch (msg) { + case WM_NCCREATE: { + CREATESTRUCTW *cs = (CREATESTRUCTW *)lp; + SetWindowLongPtrW(h, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + break; + } + case WM_SETCURSOR: { + POINT p; GetCursorPos(&p); ScreenToClient(h, &p); + BOOL move = g_moving || (!g_selecting && (InGrip(n, p.x, p.y) || (GetKeyState(VK_MENU) & 0x8000))); + SetCursor(LoadCursorW(NULL, move ? IDC_SIZEALL : IDC_IBEAM)); + return TRUE; + } + case WM_MOUSEACTIVATE: + if (n) n->active = TRUE; + return MA_ACTIVATE; + case WM_ACTIVATE: + if (!n) break; + if (LOWORD(wp) != WA_INACTIVE) { + n->active = TRUE; n->caretOn = TRUE; + SetTimer(h, TIMER_BLINK, GetCaretBlinkTime(), NULL); + RenderNote(n); + } else { + n->active = FALSE; + n->anchor = n->caret; + KillTimer(h, TIMER_BLINK); + if (n->len == 0) { PostMessageW(g_main, WM_KILLNOTE, (WPARAM)(n - g_notes), 0); return 0; } + RenderNote(n); + PushToDesktop(h); + MarkDirty(); + } + return 0; + case WM_WINDOWPOSCHANGING: + if (n && !n->active) { + WINDOWPOS *pos = (WINDOWPOS *)lp; + if (!(pos->flags & SWP_NOZORDER)) { + HWND host = FindDesktopHost(); + if (host) { + HWND above = GetWindow(host, GW_HWNDPREV); + if (above == h) pos->flags |= SWP_NOZORDER; + else pos->hwndInsertAfter = above ? above : HWND_TOP; + } + } + } + break; + case WM_TIMER: + if (wp == TIMER_BLINK && n) { n->caretOn = !n->caretOn; RenderNote(n); } + return 0; + case WM_LBUTTONDOWN: { + if (!n) return 0; + int cx = (short)LOWORD(lp), cy = (short)HIWORD(lp); + if (InGrip(n, cx, cy) || (GetKeyState(VK_MENU) & 0x8000)) { + // move the note + RECT r; GetWindowRect(h, &r); + g_dragOrig.x = r.left; g_dragOrig.y = r.top; + GetCursorPos(&g_dragStart); + g_moving = TRUE; + SetCursor(LoadCursorW(NULL, IDC_SIZEALL)); + } else { + // place the cursor / start selecting text + n->caret = CaretFromPoint(n, cx, cy); + if (!(wp & MK_SHIFT)) n->anchor = n->caret; + n->caretOn = TRUE; + g_selecting = TRUE; + RenderNote(n); + } + SetCapture(h); + return 0; + } + case WM_MOUSEMOVE: + if (n && !n->hover) { + n->hover = TRUE; + TRACKMOUSEEVENT t = { sizeof(t), TME_LEAVE, h, 0 }; + TrackMouseEvent(&t); + RenderNote(n); + } + if (GetCapture() == h && n) { + if (g_moving) { + POINT p; GetCursorPos(&p); + SetWindowPos(h, NULL, g_dragOrig.x + p.x - g_dragStart.x, g_dragOrig.y + p.y - g_dragStart.y, + 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } else if (g_selecting) { + int c = CaretFromPoint(n, (short)LOWORD(lp), (short)HIWORD(lp)); + if (c != n->caret) { n->caret = c; RenderNote(n); } + } + } + return 0; + case WM_LBUTTONUP: { + BOOL wasMove = g_moving; + g_moving = FALSE; g_selecting = FALSE; + if (GetCapture() == h) ReleaseCapture(); + if (wasMove) MarkDirty(); + return 0; + } + case WM_CAPTURECHANGED: + g_moving = FALSE; g_selecting = FALSE; + return 0; + case WM_LBUTTONDBLCLK: + if (n && !InGrip(n, (short)LOWORD(lp), (short)HIWORD(lp))) { + SelectWordAt(n, CaretFromPoint(n, (short)LOWORD(lp), (short)HIWORD(lp))); + RenderNote(n); + } + return 0; + case WM_MOUSELEAVE: + if (n && n->hover) { n->hover = FALSE; RenderNote(n); } + return 0; + case WM_RBUTTONUP: + if (n) ShowNoteMenu(h, n); + return 0; + case WM_CHAR: { + if (!n) return 0; + WCHAR c = (WCHAR)wp; + if (c == 8) { // backspace + if (HasSel(n)) DeleteSel(n); + else if (n->caret > 0) DeleteRange(n, n->caret - 1, n->caret); + } else if (c == 13) { + WCHAR nl = L'\n'; InsertText(n, &nl, 1); + } else if (c == 27) { + SetForegroundWindow(GetShellWindow()); + return 0; + } else if (c == 9 || (c >= 32 && c != 127)) { + InsertText(n, &c, 1); + } else { + return 0; + } + n->caretOn = TRUE; + RenderNote(n); + MarkDirty(); + return 0; + } + case WM_KEYDOWN: { + if (!n) return 0; + BOOL ctrl = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + BOOL shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + BOOL changed = FALSE, moved = FALSE; + int a, b; SelRange(n, &a, &b); + switch (wp) { + case VK_LEFT: + if (HasSel(n) && !shift) n->caret = a; + else if (n->caret > 0) n->caret--; + moved = TRUE; break; + case VK_RIGHT: + if (HasSel(n) && !shift) n->caret = b; + else if (n->caret < n->len) n->caret++; + moved = TRUE; break; + case VK_UP: MoveCaretVertical(n, -1); moved = TRUE; break; + case VK_DOWN: MoveCaretVertical(n, +1); moved = TRUE; break; + case VK_HOME: + if (ctrl) n->caret = 0; + else while (n->caret > 0 && n->text[n->caret - 1] != L'\n') n->caret--; + moved = TRUE; break; + case VK_END: + if (ctrl) n->caret = n->len; + else while (n->caret < n->len && n->text[n->caret] != L'\n') n->caret++; + moved = TRUE; break; + case VK_DELETE: + if (HasSel(n)) DeleteSel(n); + else if (n->caret < n->len) DeleteRange(n, n->caret, n->caret + 1); + changed = TRUE; break; + case 'A': if (!ctrl) return 0; n->anchor = 0; n->caret = n->len; break; + case 'C': if (ctrl) CopyNote(h, n, FALSE); return 0; + case 'X': + if (!ctrl) return 0; + CopyNote(h, n, FALSE); + if (HasSel(n)) DeleteSel(n); else { n->anchor = 0; n->caret = n->len; DeleteSel(n); } + changed = TRUE; break; + case 'V': if (!ctrl) return 0; PasteFromClipboard(h, n); changed = TRUE; break; + default: return 0; + } + if (moved && !shift) n->anchor = n->caret; + n->caretOn = TRUE; + RenderNote(n); + if (changed) MarkDirty(); + return 0; + } + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- desktop double-click detection ---------------- */ + +static BOOL IsDesktopSurface(HWND w) { + if (!w) return FALSE; + WCHAR cls[64], pcls[64] = L""; + GetClassNameW(w, cls, 64); + HWND p = GetParent(w); + if (p) GetClassNameW(p, pcls, 64); + if (!wcscmp(cls, L"SysListView32") && !wcscmp(pcls, L"SHELLDLL_DefView")) return TRUE; + if (!wcscmp(cls, L"SHELLDLL_DefView") || !wcscmp(cls, L"Progman") || !wcscmp(cls, L"WorkerW")) return TRUE; + return FALSE; +} + +// TRUE if a desktop icon is under the point (so a double-click should open it, not start a note) +static BOOL DesktopIconAt(POINT pt) { + IAccessible *acc = NULL; + VARIANT child; VariantInit(&child); + if (FAILED(AccessibleObjectFromPoint(pt, &acc, &child)) || !acc) return FALSE; + BOOL icon = FALSE; + VARIANT role; VariantInit(&role); + if (SUCCEEDED(IAccessible_get_accRole(acc, child, &role)) && role.vt == VT_I4) { + long r = role.lVal; + icon = (r == ROLE_SYSTEM_LISTITEM || r == ROLE_SYSTEM_STATICTEXT || r == ROLE_SYSTEM_GRAPHIC || + r == ROLE_SYSTEM_TEXT || r == ROLE_SYSTEM_PUSHBUTTON); + } + VariantClear(&role); + VariantClear(&child); + IAccessible_Release(acc); + return icon; +} + +int QN_Mouse(WPARAM wp, MSLLHOOKSTRUCT *m) { + if (!g_enabled || wp != WM_LBUTTONDOWN) return 0; + HWND w = WindowFromPoint(m->pt); + if (IsDesktopSurface(w)) { + DWORD dt = m->time - g_lastDownTime; + if (g_lastDownTime && dt <= GetDoubleClickTime() && + abs(m->pt.x - g_lastDownPt.x) <= GetSystemMetrics(SM_CXDOUBLECLK) && + abs(m->pt.y - g_lastDownPt.y) <= GetSystemMetrics(SM_CYDOUBLECLK)) { + PostMessageW(g_main, WM_DESKDBL, 0, MAKELPARAM((WORD)(SHORT)m->pt.x, (WORD)(SHORT)m->pt.y)); + g_lastDownTime = 0; + } else { + g_lastDownTime = m->time ? m->time : 1; + g_lastDownPt = m->pt; + } + } else { + g_lastDownTime = 0; + } + return 0; // never swallows: desktop keeps working normally +} + +/* ---------------- tray + main ---------------- */ + +static void Balloon(const WCHAR *title, const WCHAR *text) { DT_Balloon(title, text); } + +static void AddTrayIcon(void) { } + +static void ShowHelp(void) { + MessageBoxW(NULL, + L"Write on your desktop:\n" + L" Double-click any empty spot on the wallpaper and start typing.\n" + L" Click anywhere else when you're done. The text stays on the desktop,\n" + L" behind your windows, just like desktop icons.\n\n" + L"Copy text out of a note:\n" + L" Drag across the text to select it, then press Ctrl+C.\n" + L" Double-click a word to select it. Ctrl+A selects everything.\n" + L" Or right-click the note and choose Copy all text.\n\n" + L"Move a note:\n" + L" Drag the small gray square at its top left corner (it shows when\n" + L" you point at the note), or hold Alt and drag anywhere on it.\n\n" + L"Right-click a note to change its color or delete it.\n" + L"Enter starts a new line. Esc finishes editing.\n" + L"A note you empty out disappears on its own.\n" + L"Notes save automatically and come back after a restart.", + L"How to use QuickNote", MB_OK | MB_ICONINFORMATION); +} + +static void ShowTrayMenu(void) { + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"QuickNote"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_NEW, L"New note"); + AppendMenuW(m, MF_STRING | (g_hidden ? MF_CHECKED : 0), IDM_HIDE, L"Hide all notes"); + AppendMenuW(m, MF_STRING | (StartupEnabled() ? MF_CHECKED : 0), IDM_STARTUP, L"Start with Windows"); + AppendMenuW(m, MF_STRING, IDM_HELP, L"How to use"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_EXIT, L"Exit"); + POINT p; GetCursorPos(&p); + SetForegroundWindow(g_main); + TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_BOTTOMALIGN, p.x, p.y, 0, g_main, NULL); + PostMessageW(g_main, WM_NULL, 0, 0); + DestroyMenu(m); +} + +static void NewNoteAt(int x, int y) { + if (g_hidden) SetAllHidden(FALSE); + HDC dc = CreateCompatibleDC(NULL); + SelectObject(dc, g_font); + TEXTMETRICW tm; GetTextMetricsW(dc, &tm); + DeleteDC(dc); + // place it so the cursor lands right where you clicked + CreateNote(x - GripW() - S(3), y - S(4) - tm.tmHeight / 2, L"", 0, COLORS[g_lastColor], TRUE); +} + +static LRESULT CALLBACK MainProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == g_taskbarCreated && g_taskbarCreated) { AddTrayIcon(); return 0; } + switch (msg) { + case WM_TRAY: + if (LOWORD(lp) == WM_RBUTTONUP || LOWORD(lp) == WM_LBUTTONUP) ShowTrayMenu(); + return 0; + case WM_DESKDBL: { + POINT pt = { (short)LOWORD(lp), (short)HIWORD(lp) }; + if (!DesktopIconAt(pt)) NewNoteAt(pt.x, pt.y); + return 0; + } + case WM_KILLNOTE: + if (wp < MAX_NOTES) DeleteNote(&g_notes[wp]); + return 0; + case WM_TIMER: + if (wp == TIMER_SAVE) { KillTimer(h, TIMER_SAVE); if (g_dirty) SaveNotes(); } + else if (wp == TIMER_CHECK && !g_hidden) { + // keep notes visible after Win+D and sitting just above the wallpaper + for (int i = 0; i < MAX_NOTES; i++) { + Note *n = &g_notes[i]; + if (!n->used) continue; + if (IsIconic(n->hwnd) || !IsWindowVisible(n->hwnd)) ShowWindow(n->hwnd, SW_SHOWNA); + } + HWND fg = GetForegroundWindow(); + if (fg != g_lastFg) { + g_lastFg = fg; + if (IsDesktopClass(fg)) + for (int i = 0; i < MAX_NOTES; i++) + if (g_notes[i].used && !g_notes[i].active) PushToDesktop(g_notes[i].hwnd); + } + } + return 0; + case WM_COMMAND: + switch (LOWORD(wp)) { + case IDM_NEW: { + RECT wa; SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0); + NewNoteAt((wa.left + wa.right) / 2 - S(60), (wa.top + wa.bottom) / 2); + break; + } + case IDM_HIDE: SetAllHidden(!g_hidden); break; + case IDM_STARTUP: SetStartup(!StartupEnabled()); break; + case IDM_HELP: ShowHelp(); break; + case IDM_EXIT: DestroyWindow(h); break; + } + return 0; + case WM_QUERYENDSESSION: + SaveNotes(); + return TRUE; + case WM_DESTROY: + SaveNotes(); + if (g_hook) UnhookWindowsHookEx(g_hook); + Shell_NotifyIconW(NIM_DELETE, &g_nid); + + return 0; + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- DeskTools module interface ---------------- */ + +void QN_SetEnabled(BOOL on) { + g_enabled = on; + SetAllHidden(!on); +} +BOOL QN_IsEnabled(void) { return g_enabled; } + +void QN_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_font = CreateFontW(-MulDiv(FONT_PT, g_dpi, 72), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, + DEFAULT_PITCH, L"Segoe UI"); + WCHAR dir[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, dir))) { + wcscat(dir, L"\\QuickNote"); + CreateDirectoryW(dir, NULL); + swprintf(g_file, MAX_PATH, L"%ls\\notes.txt", dir); + } else { + GetTempPathW(MAX_PATH, dir); + swprintf(g_file, MAX_PATH, L"%lsQuickNote_notes.txt", dir); + } + DWORD lc = RegGetFlag(L"LastColor"); + if (lc < NCOLORS) g_lastColor = (int)lc; + + WNDCLASSEXW wc; ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); wc.lpfnWndProc = MainProc; wc.hInstance = inst; wc.lpszClassName = L"QuickNoteMain"; + RegisterClassExW(&wc); + WNDCLASSEXW nc; ZeroMemory(&nc, sizeof(nc)); + nc.cbSize = sizeof(nc); nc.style = CS_DBLCLKS; nc.lpfnWndProc = NoteProc; nc.hInstance = inst; + nc.hCursor = LoadCursorW(NULL, IDC_IBEAM); nc.lpszClassName = L"QuickNoteText"; + RegisterClassExW(&nc); + + g_main = CreateWindowExW(0, L"QuickNoteMain", APP_NAME, 0, 0, 0, 0, 0, NULL, NULL, inst, NULL); + g_hidden = !enabled; + LoadNotes(); + SetTimer(g_main, TIMER_CHECK, 1000, NULL); + g_enabled = enabled; +} + +void QN_Shutdown(void) { + if (g_main) { DestroyWindow(g_main); g_main = NULL; } // saves the notes + for (int i = 0; i < MAX_NOTES; i++) if (g_notes[i].used && g_notes[i].hwnd) DestroyWindow(g_notes[i].hwnd); + if (g_font) { DeleteObject(g_font); g_font = NULL; } +} + +void QN_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_NEW, L"New note"); + AppendMenuW(m, MF_STRING | dis | (g_hidden && g_enabled ? MF_CHECKED : 0), base + IDM_HIDE, L"Hide all notes"); + AppendMenuW(parent, MF_POPUP, (UINT_PTR)m, L"QuickNote"); +} + +void QN_Command(UINT local) { + if (local == DT_ID_ENABLE) { QN_SetEnabled(!g_enabled); return; } + SendMessageW(g_main, WM_COMMAND, local, 0); +} diff --git a/st.c b/st.c new file mode 100644 index 0000000..7c0d5e1 --- /dev/null +++ b/st.c @@ -0,0 +1,486 @@ +// SeeThru: make any window see-through, all the way down to 5% opacity. +// Ctrl+Shift + mouse wheel over a window change that window's opacity +// Ctrl+Alt+PageDown / PageUp more / less see-through (active window) +// Ctrl+Alt+Home back to fully solid (active window) +// Ctrl+Alt+End ghost mode: click-through and always on top + +#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 APP_NAME L"SeeThru" +#define WM_TRAY (WM_APP + 1) +#define WM_WHEELADJ (WM_APP + 2) +#define MAX_ENT 128 +#define MIN_PCT 5 +#define STEP_PCT 5 + +#define HK_MORE 1 +#define HK_LESS 2 +#define HK_RESET 3 +#define HK_GHOST 4 + +#define TIMER_OSD 1 +#define TIMER_CLEAN 2 + +#define IDM_RESETALL 100 +#define IDM_STARTUP 101 +#define IDM_HELP 102 +#define IDM_EXIT 103 +#define IDM_WIN_BASE 1000 + +typedef struct { + BOOL used; + HWND h; + LONG origEx; // extended style before we touched it + BOOL hadLayered; // window was already layered (alpha based) + BYTE origAlpha; + DWORD origFlags; + COLORREF origKey; + int pct; // current opacity percent + BOOL ghost; + BOOL wasTopmost; +} Ent; + +static Ent g_ent[MAX_ENT]; +static BOOL g_enabled = TRUE; +static HINSTANCE g_inst; +static HWND g_main, g_osd; +static HHOOK g_hook; +static NOTIFYICONDATAW g_nid; +static UINT g_taskbarCreated; +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); } + +/* ---------------- startup setting ---------------- */ + +static const WCHAR *RUN_KEY = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; +static BOOL StartupEnabled(void) { + WCHAR buf[MAX_PATH * 2]; DWORD sz = sizeof(buf); + return RegGetValueW(HKEY_CURRENT_USER, RUN_KEY, APP_NAME, RRF_RT_REG_SZ, NULL, buf, &sz) == ERROR_SUCCESS; +} +static void SetStartup(BOOL on) { + HKEY k; + if (RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, KEY_WRITE, &k) != ERROR_SUCCESS) return; + if (on) { + WCHAR exe[MAX_PATH], val[MAX_PATH + 4]; + GetModuleFileNameW(NULL, exe, MAX_PATH); + swprintf(val, MAX_PATH + 4, L"\"%ls\"", exe); + RegSetValueExW(k, APP_NAME, 0, REG_SZ, (BYTE *)val, (DWORD)((wcslen(val) + 1) * sizeof(WCHAR))); + } else { + RegDeleteValueW(k, APP_NAME); + } + RegCloseKey(k); +} +static DWORD RegGetFlag(const WCHAR *name) { + DWORD v = 0, sz = sizeof(v); + RegGetValueW(HKEY_CURRENT_USER, L"Software\\SeeThru", name, RRF_RT_REG_DWORD, NULL, &v, &sz); + return v; +} +static void RegSetFlag(const WCHAR *name, DWORD v) { + HKEY k; + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SeeThru", 0, NULL, 0, KEY_WRITE, NULL, &k, NULL) == ERROR_SUCCESS) { + RegSetValueExW(k, name, 0, REG_DWORD, (BYTE *)&v, sizeof(v)); + RegCloseKey(k); + } +} + +/* ---------------- tray ---------------- */ + +static void Balloon(const WCHAR *title, const WCHAR *text) { DT_Balloon(title, text); } + +static void AddTrayIcon(void) { } + +/* ---------------- on-screen indicator ---------------- */ + +static void ShowOsd(HWND target, const WCHAR *text) { + wcsncpy(g_osdText, text, 127); g_osdText[127] = 0; + int w = S(220), h = S(46); + RECT r; + if (target && IsWindow(target) && GetWindowRect(target, &r) && r.right > r.left) { + // center on the target window, clamped to its monitor + HMONITOR mon = MonitorFromWindow(target, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; GetMonitorInfoW(mon, &mi); + int cx = (r.left + r.right) / 2, cy = (r.top + r.bottom) / 2; + if (cx < mi.rcWork.left + w / 2) cx = mi.rcWork.left + w / 2; + if (cx > mi.rcWork.right - w / 2) cx = mi.rcWork.right - w / 2; + if (cy < mi.rcWork.top + h / 2) cy = mi.rcWork.top + h / 2; + if (cy > mi.rcWork.bottom - h / 2) cy = mi.rcWork.bottom - h / 2; + r.left = cx - w / 2; r.top = cy - h / 2; + } else { + POINT p; GetCursorPos(&p); + r.left = p.x - w / 2; r.top = p.y + S(24); + } + HRGN rgn = CreateRoundRectRgn(0, 0, w + 1, h + 1, S(12), S(12)); + SetWindowRgn(g_osd, rgn, FALSE); + SetWindowPos(g_osd, HWND_TOPMOST, r.left, r.top, w, h, SWP_NOACTIVATE | SWP_SHOWWINDOW); + InvalidateRect(g_osd, NULL, TRUE); + UpdateWindow(g_osd); + SetTimer(g_main, TIMER_OSD, 1100, NULL); +} + +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); +} + +/* ---------------- transparency core ---------------- */ + +static Ent *FindEnt(HWND h) { + for (int i = 0; i < MAX_ENT; i++) if (g_ent[i].used && g_ent[i].h == h) return &g_ent[i]; + return NULL; +} + +static BOOL Allowed(HWND h) { + if (!h || !IsWindow(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")) return FALSE; + return TRUE; +} + +// Creates (or returns) the tracking entry. Returns NULL if the window can't be handled. +static Ent *GetEnt(HWND h, const WCHAR **err) { + Ent *e = FindEnt(h); + if (e) return e; + LONG ex = GetWindowLongW(h, GWL_EXSTYLE); + BYTE a = 255; DWORD flags = 0; COLORREF key = 0; + BOOL hadLayered = (ex & WS_EX_LAYERED) != 0; + if (hadLayered) { + if (!GetLayeredWindowAttributes(h, &key, &a, &flags)) { + *err = L"This window draws its own transparency"; + return NULL; + } + if (!(flags & LWA_ALPHA)) a = 255; + } + for (int i = 0; i < MAX_ENT; i++) { + if (!g_ent[i].used) { + e = &g_ent[i]; + ZeroMemory(e, sizeof(*e)); + e->used = TRUE; e->h = h; e->origEx = ex; + e->hadLayered = hadLayered; e->origAlpha = a; e->origFlags = flags; e->origKey = key; + e->pct = (a * 100 + 127) / 255; + e->wasTopmost = (ex & WS_EX_TOPMOST) != 0; + return e; + } + } + *err = L"Too many windows"; + return NULL; +} + +static void RestoreEnt(Ent *e) { + if (!e->used) return; + HWND h = e->h; + if (IsWindow(h)) { + if (e->ghost && !e->wasTopmost) + SetWindowPos(h, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + LONG cur = GetWindowLongW(h, GWL_EXSTYLE); + LONG want = (cur & ~(WS_EX_LAYERED | WS_EX_TRANSPARENT)) | + (e->origEx & (WS_EX_LAYERED | WS_EX_TRANSPARENT)); + if (e->hadLayered) { + SetWindowLongW(h, GWL_EXSTYLE, want); + SetLayeredWindowAttributes(h, e->origKey, e->origAlpha, e->origFlags); + } else { + SetLayeredWindowAttributes(h, 0, 255, LWA_ALPHA); + SetWindowLongW(h, GWL_EXSTYLE, want); + } + RedrawWindow(h, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + } + e->used = FALSE; +} + +static void ApplyEnt(Ent *e) { + HWND h = e->h; + LONG ex = GetWindowLongW(h, GWL_EXSTYLE) | WS_EX_LAYERED; + if (e->ghost) ex |= WS_EX_TRANSPARENT; + else ex = (ex & ~WS_EX_TRANSPARENT) | (e->origEx & WS_EX_TRANSPARENT); + SetWindowLongW(h, GWL_EXSTYLE, ex); + BYTE a = (BYTE)((e->pct * 255 + 50) / 100); + DWORD flags = LWA_ALPHA; + if (e->hadLayered && (e->origFlags & LWA_COLORKEY)) flags |= LWA_COLORKEY; + SetLayeredWindowAttributes(h, e->origKey, a, flags); +} + +static BOOL Writable(HWND h) { + // A window we can't change (admin program) silently ignores SetWindowLong. + LONG ex = GetWindowLongW(h, GWL_EXSTYLE); + return (ex & WS_EX_LAYERED) != 0; +} + +static void ChangeOpacity(HWND h, int deltaPct) { + if (!Allowed(h)) return; + const WCHAR *err = NULL; + Ent *e = GetEnt(h, &err); + if (!e) { ShowOsd(h, err); return; } + int pct = e->pct + deltaPct; + if (pct < MIN_PCT) pct = MIN_PCT; + if (pct > 100) pct = 100; + e->pct = pct; + if (pct >= 100 && !e->ghost) { + RestoreEnt(e); + ShowOsd(h, L"Opacity 100%"); + return; + } + ApplyEnt(e); + if (!Writable(h)) { + e->used = FALSE; + ShowOsd(h, L"Can't change this window (admin program?)"); + return; + } + WCHAR t[64]; + swprintf(t, 64, e->ghost ? L"Opacity %d%% (ghost)" : L"Opacity %d%%", pct); + ShowOsd(h, t); +} + +static void ResetWindow(HWND h) { + Ent *e = FindEnt(h); + if (e) RestoreEnt(e); + ShowOsd(h, L"Opacity 100%"); +} + +static void ToggleGhost(HWND h) { + if (!Allowed(h)) return; + const WCHAR *err = NULL; + Ent *e = GetEnt(h, &err); + if (!e) { ShowOsd(h, err); return; } + if (!e->ghost) { + e->ghost = TRUE; + if (e->pct > 60) e->pct = 60; // ghosts should be see-through so you can tell + ApplyEnt(e); + if (!Writable(h)) { e->used = FALSE; ShowOsd(h, L"Can't change this window (admin program?)"); return; } + SetWindowPos(h, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + WCHAR t[64]; swprintf(t, 64, L"Ghost mode on (%d%%)", e->pct); + ShowOsd(h, t); + } else { + e->ghost = FALSE; + if (!e->wasTopmost) SetWindowPos(h, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + if (e->pct >= 100) RestoreEnt(e); else ApplyEnt(e); + ShowOsd(h, L"Ghost mode off"); + } +} + +static void ResetAll(void) { + for (int i = 0; i < MAX_ENT; i++) if (g_ent[i].used) RestoreEnt(&g_ent[i]); +} + +/* ---------------- mouse wheel hook ---------------- */ + +int ST_Mouse(WPARAM wp, MSLLHOOKSTRUCT *m) { + if (!g_enabled || wp != WM_MOUSEWHEEL) return 0; + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && (GetAsyncKeyState(VK_SHIFT) & 0x8000) && + !(GetAsyncKeyState(VK_MENU) & 0x8000)) { + HWND w = WindowFromPoint(m->pt); + HWND root = w ? GetAncestor(w, GA_ROOT) : NULL; + if (root) { + short delta = (short)HIWORD(m->mouseData); + PostMessageW(g_main, WM_WHEELADJ, (WPARAM)root, (LPARAM)delta); + return 1; // swallow so the app underneath doesn't zoom or scroll + } + } + return 0; +} + +/* ---------------- menus ---------------- */ + +static void ShowHelp(void) { + MessageBoxW(NULL, + L"Change how see-through a window is:\n" + L" Hold Ctrl+Shift and scroll the mouse wheel over any window.\n" + L" Scroll down for more see-through, up for more solid.\n\n" + L"Keyboard (works on the active window):\n" + L" Ctrl+Alt+Page Down more see-through\n" + L" Ctrl+Alt+Page Up more solid\n" + L" Ctrl+Alt+Home back to fully solid\n" + L" Ctrl+Alt+End ghost mode on or off\n\n" + L"Ghost mode keeps the window on top of everything and lets your\n" + L"clicks pass straight through it to whatever is underneath.\n" + L"To turn ghost mode off, click the window's taskbar button so it\n" + L"becomes active, then press Ctrl+Alt+End again. You can also pick\n" + L"the window from the SeeThru tray menu to reset it.\n\n" + L"Windows can go as low as 5% opacity.\n" + L"Exiting SeeThru puts every window back to normal.", + L"How to use SeeThru", MB_OK | MB_ICONINFORMATION); +} + +static void ShowTrayMenu(void) { + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"SeeThru"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + int n = 0; + for (int i = 0; i < MAX_ENT; i++) { + Ent *e = &g_ent[i]; + if (!e->used || !IsWindow(e->h)) continue; + WCHAR title[80], item[128]; + GetWindowTextW(e->h, title, 60); + if (!title[0]) wcscpy(title, L"(untitled)"); + swprintf(item, 128, L"Reset: %ls (%d%%%ls)", title, e->pct, e->ghost ? L", ghost" : L""); + AppendMenuW(m, MF_STRING, IDM_WIN_BASE + i, item); + n++; + } + if (n) AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING | (n ? 0 : MF_GRAYED), IDM_RESETALL, L"Reset all windows"); + AppendMenuW(m, MF_STRING | (StartupEnabled() ? MF_CHECKED : 0), IDM_STARTUP, L"Start with Windows"); + AppendMenuW(m, MF_STRING, IDM_HELP, L"How to use"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + AppendMenuW(m, MF_STRING, IDM_EXIT, L"Exit (puts windows back to normal)"); + POINT p; GetCursorPos(&p); + SetForegroundWindow(g_main); + TrackPopupMenu(m, TPM_RIGHTBUTTON | TPM_BOTTOMALIGN, p.x, p.y, 0, g_main, NULL); + PostMessageW(g_main, WM_NULL, 0, 0); + DestroyMenu(m); +} + +/* ---------------- main window ---------------- */ + +static LRESULT CALLBACK MainProc(HWND h, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == g_taskbarCreated && g_taskbarCreated) { AddTrayIcon(); return 0; } + switch (msg) { + case WM_TRAY: + if (LOWORD(lp) == WM_RBUTTONUP || LOWORD(lp) == WM_LBUTTONUP) ShowTrayMenu(); + return 0; + case WM_WHEELADJ: { + int notches = (int)(short)lp / WHEEL_DELTA; + if (notches == 0) notches = ((short)lp > 0) ? 1 : -1; + ChangeOpacity((HWND)wp, notches * STEP_PCT); // wheel up = more solid + return 0; + } + case WM_HOTKEY: { + HWND fg = GetForegroundWindow(); + if (fg) fg = GetAncestor(fg, GA_ROOT); + switch (wp) { + case HK_MORE: ChangeOpacity(fg, -STEP_PCT); break; + case HK_LESS: ChangeOpacity(fg, +STEP_PCT); break; + case HK_RESET: if (Allowed(fg)) ResetWindow(fg); break; + case HK_GHOST: ToggleGhost(fg); break; + } + return 0; + } + case WM_TIMER: + if (wp == TIMER_OSD) { KillTimer(h, TIMER_OSD); ShowWindow(g_osd, SW_HIDE); } + else if (wp == TIMER_CLEAN) { + for (int i = 0; i < MAX_ENT; i++) if (g_ent[i].used && !IsWindow(g_ent[i].h)) g_ent[i].used = FALSE; + } + return 0; + case WM_COMMAND: { + UINT id = LOWORD(wp); + if (id >= IDM_WIN_BASE && id < IDM_WIN_BASE + MAX_ENT) { + Ent *e = &g_ent[id - IDM_WIN_BASE]; + if (e->used) RestoreEnt(e); + return 0; + } + switch (id) { + case IDM_RESETALL: ResetAll(); break; + case IDM_STARTUP: SetStartup(!StartupEnabled()); break; + case IDM_HELP: ShowHelp(); break; + case IDM_EXIT: DestroyWindow(h); break; + } + return 0; + } + case WM_QUERYENDSESSION: + ResetAll(); + return TRUE; + case WM_DESTROY: + ResetAll(); + if (g_hook) UnhookWindowsHookEx(g_hook); + for (int i = HK_MORE; i <= HK_GHOST; i++) UnregisterHotKey(h, i); + Shell_NotifyIconW(NIM_DELETE, &g_nid); + + return 0; + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- DeskTools module interface ---------------- */ + +static BOOL g_hkOk[5]; +void ST_SetEnabled(BOOL on) { + static const UINT vks[5] = { 0, VK_NEXT, VK_PRIOR, VK_HOME, VK_END }; + g_enabled = on; + for (int i = HK_MORE; i <= HK_GHOST; i++) { + if (on && !g_hkOk[i]) g_hkOk[i] = RegisterHotKey(g_main, i, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, vks[i]); + if (!on && g_hkOk[i]) { UnregisterHotKey(g_main, i); g_hkOk[i] = FALSE; } + } + if (!on) ResetAll(); +} +BOOL ST_IsEnabled(void) { return g_enabled; } + +void ST_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(12, 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"SeeThruMain"; + RegisterClassExW(&wc); + WNDCLASSEXW oc; ZeroMemory(&oc, sizeof(oc)); + oc.cbSize = sizeof(oc); oc.lpfnWndProc = OsdProc; oc.hInstance = inst; oc.lpszClassName = L"SeeThruOSD"; + oc.hCursor = LoadCursorW(NULL, IDC_ARROW); + RegisterClassExW(&oc); + g_main = CreateWindowExW(0, L"SeeThruMain", APP_NAME, 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"SeeThruOSD", L"", WS_POPUP, 0, 0, 10, 10, NULL, NULL, inst, NULL); + SetLayeredWindowAttributes(g_osd, 0, 225, LWA_ALPHA); + SetTimer(g_main, TIMER_CLEAN, 5000, NULL); + g_enabled = !enabled; + ST_SetEnabled(enabled); +} + +void ST_Shutdown(void) { + 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 ST_AppendMenu(HMENU parent, UINT base) { + HMENU m = CreatePopupMenu(); + AppendMenuW(m, MF_STRING | (g_enabled ? MF_CHECKED : 0), base + DT_ID_ENABLE, L"Turned on"); + AppendMenuW(m, MF_SEPARATOR, 0, NULL); + int n = 0; + for (int i = 0; i < MAX_ENT; i++) { + Ent *e = &g_ent[i]; + if (!e->used || !IsWindow(e->h)) continue; + WCHAR title[80], item[128]; + GetWindowTextW(e->h, title, 60); + if (!title[0]) wcscpy(title, L"(untitled)"); + swprintf(item, 128, L"Reset: %ls (%d%%%ls)", title, e->pct, e->ghost ? L", ghost" : L""); + AppendMenuW(m, MF_STRING, base + IDM_WIN_BASE + i, item); + n++; + } + AppendMenuW(m, MF_STRING | (n ? 0 : MF_GRAYED), base + IDM_RESETALL, L"Reset all windows"); + AppendMenuW(parent, MF_POPUP, (UINT_PTR)m, L"SeeThru"); +} + +void ST_Command(UINT local) { + if (local == DT_ID_ENABLE) { ST_SetEnabled(!g_enabled); return; } + SendMessageW(g_main, WM_COMMAND, local, 0); +}