From 92c3dc7022fc8e89215d0839112e28b2ac953746 Mon Sep 17 00:00:00 2001 From: weishaupt Date: Sun, 4 Oct 2026 14:27:32 +0000 Subject: [PATCH] Initial commit: DeskIcons source --- README.md | 37 +++ app.ico | Bin 0 -> 19479 bytes build.sh | 7 + deskicons.c | 729 ++++++++++++++++++++++++++++++++++++++++++++++++++++ res.rc | 23 ++ 5 files changed, 796 insertions(+) create mode 100644 README.md create mode 100644 app.ico create mode 100644 build.sh create mode 100644 deskicons.c create mode 100644 res.rc diff --git a/README.md b/README.md new file mode 100644 index 0000000..b47bc80 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# DeskIcons + +A tiny Windows utility (about 120 KB, no installer) that sends any window to the desktop as a draggable label, the way Windows 3.1 used to put minimized programs on the desktop. + +## Using it + +Run `DeskIcons.exe`. It lives in the system tray. + +- **Send a window to the desktop:** hold Shift and click the window's minimize button, or press Ctrl+Alt+I while the window is active. The window leaves the taskbar and appears on the desktop as a label with its title. +- **Move it:** drag the label anywhere. +- **Bring it back:** double-click the label. +- **Right-click a label** to restore or close that window. + +Tray menu options: + +- Restore all windows +- Show program icons (off by default, text only) +- Keep icons on top of windows (off by default, labels sit at desktop level) +- Start with Windows +- Exit (brings every held window back) + +If DeskIcons is closed unexpectedly, it remembers which windows it was holding and rebuilds their labels the next time it starts. + +## Notes + +- Windows from programs running as administrator can only be grabbed if DeskIcons also runs as administrator. +- The exe is unsigned, so Windows SmartScreen may warn the first time. Choose "More info" and then "Run anyway." + +## Building + +Plain Win32 C, no dependencies beyond the standard Windows DLLs. + +``` +sh build.sh +``` + +Requires MinGW-w64 (`x86_64-w64-mingw32-gcc` and `windres`). diff --git a/app.ico b/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..8880322a35d29e7ecb40e8527fb33f6bca953857 GIT binary patch literal 19479 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 +#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 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) { + int n = CountItems(); + if (n == 0) swprintf(g_nid.szTip, 128, L"DeskIcons"); + else swprintf(g_nid.szTip, 128, L"DeskIcons (%d window%ls on the desktop)", n, n == 1 ? L"" : L"s"); + g_nid.uFlags = NIF_TIP; + Shell_NotifyIconW(NIM_MODIFY, &g_nid); +} + +static void Balloon(const WCHAR *title, const WCHAR *text) { + 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 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) { + 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"DeskIcons"); + Shell_NotifyIconW(NIM_ADD, &g_nid); + UpdateTrayTip(); +} + +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); + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(h, msg, wp, lp); +} + +/* ---------------- shift+click on minimize button ---------------- */ + +static LRESULT CALLBACK MouseLL(int code, WPARAM wp, LPARAM lp) { + if (code == HC_ACTION) { + MSLLHOOKSTRUCT *m = (MSLLHOOKSTRUCT *)lp; + 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 CallNextHookEx(g_mouseHook, code, wp, lp); +} + +/* ---------------- entry ---------------- */ + +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"DeskIcons_SingleInstance_Mutex"); + if (GetLastError() == ERROR_ALREADY_EXISTS) { + MessageBoxW(NULL, L"DeskIcons is already running. Look for its icon in the system tray.", APP_NAME, MB_OK | MB_ICONINFORMATION); + return 0; + } + + SetDpiCtxFn setCtx = (SetDpiCtxFn)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetProcessDpiAwarenessContext"); + if (!setCtx || !setCtx((HANDLE)-4)) SetProcessDPIAware(); + 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 = { sizeof(wc) }; + wc.lpfnWndProc = MainProc; wc.hInstance = inst; wc.lpszClassName = L"DeskIconsMain"; + wc.hIcon = LoadIconW(inst, MAKEINTRESOURCEW(1)); + RegisterClassExW(&wc); + + WNDCLASSEXW ic = { 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); + g_taskbarCreated = RegisterWindowMessageW(L"TaskbarCreated"); + AddTrayIcon(); + + g_hotkeyOk = RegisterHotKey(g_main, HOTKEY_ID, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 'I'); + g_mouseHook = SetWindowsHookExW(WH_MOUSE_LL, MouseLL, inst, 0); + SetTimer(g_main, TIMER_CHECK, 1000, NULL); + + RecoverFromStateFile(); + UpdateTrayTip(); + + if (!RegGetDword(L"Welcomed", 0)) { + RegSetDword(L"Welcomed", 1); + Balloon(L"DeskIcons is running", + L"Shift+click any minimize button (or press Ctrl+Alt+I) to send that window to the desktop as an icon."); + } else if (!g_hotkeyOk) { + Balloon(L"DeskIcons", L"Ctrl+Alt+I is already taken by another program. Shift+click on minimize buttons still works."); + } + + MSG m; + while (GetMessageW(&m, NULL, 0, 0) > 0) { + TranslateMessage(&m); + DispatchMessageW(&m); + } + if (g_font) DeleteObject(g_font); + if (mutex) { ReleaseMutex(mutex); CloseHandle(mutex); } + return 0; +} diff --git a/res.rc b/res.rc new file mode 100644 index 0000000..be45e67 --- /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", "DeskIcons - send windows to the desktop as icons" + VALUE "ProductName", "DeskIcons" + VALUE "FileVersion", "1.0.0" + VALUE "ProductVersion", "1.0.0" + VALUE "OriginalFilename", "DeskIcons.exe" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END