commit 92c3dc7022fc8e89215d0839112e28b2ac953746 Author: weishaupt Date: Sun Oct 4 14:27:32 2026 +0000 Initial commit: DeskIcons source 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 0000000..8880322 Binary files /dev/null and b/app.ico differ diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..1b28c39 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Cross-compile DeskIcons.exe with MinGW-w64 (Linux) or MSYS2 (Windows). +set -e +x86_64-w64-mingw32-windres res.rc -O coff -o res.o +x86_64-w64-mingw32-gcc -Os -s -municode -mwindows deskicons.c res.o -o DeskIcons.exe \ + -luser32 -lgdi32 -lshell32 -ladvapi32 -static-libgcc +echo "Built DeskIcons.exe" diff --git a/deskicons.c b/deskicons.c new file mode 100644 index 0000000..a9cc17f --- /dev/null +++ b/deskicons.c @@ -0,0 +1,729 @@ +// 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 +#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