#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00   // Windows 10/11 - needed for ChangeWindowMessageFilterEx etc.
#endif
#include <windows.h>
#include <shellapi.h>
#include <stdbool.h>
#include <stdlib.h>
#include <wchar.h>

// =====================================================================
//  SmoothDrag - global window dragging / resizing utility
//  ---------------------------------------------------------------------
//  WHAT IT DOES: double-click and hold anywhere inside (almost) any
//  window to drag it - not just by its title bar, and it works even on
//  windows that have no title bar at all. Ctrl+click and hold anywhere
//  to resize from whichever corner that click is closest to (the click
//  point's quadrant relative to the window's center decides the corner
//  - it's not about literal pixel proximity to an actual corner). Alt+click and hold anywhere (the
//  Linux window-manager convention) also moves the window immediately,
//  no double-click needed - this one is toggleable from the tray menu,
//  since several graphics/3D applications use Alt+drag themselves (e.g.
//  camera orbit/pan tools), and a global hook would otherwise compete
//  with them for the same gesture. A tray icon menu offers "start with
//  Windows" (registers a Task Scheduler task), the Alt-drag toggle, and
//  "exit".
//
// =====================================================================

#define WM_TRAYICON       (WM_APP + 1)
#define ID_TRAY_EXIT      1001
#define ID_TRAY_AUTOSTART 1002
#define ID_TRAY_ALTDRAG   1003
#define ID_TRAY_WINDRAG   1004
#define TRAY_ICON_UID     1001
#define APP_ICON_RES_ID   101   // matches "101 ICON "icon.ico"" in resource.rc
#define AUTOSTART_TASK_NAME L"SmoothDrag_AutoStart"
#define SETTINGS_REGKEY    L"Software\\SmoothDrag"
#define ALTDRAG_REGVALUE   L"AltDragEnabled"
#define WINDRAG_REGVALUE   L"WinDragEnabled"

typedef enum { DRAG_NONE = 0, DRAG_MOVE, DRAG_RESIZE } DragMode;

static HHOOK  g_hHook         = NULL;   // WH_MOUSE_LL
static HHOOK  g_hKeyboardHook = NULL;   // WH_KEYBOARD_LL - exists solely to suppress the
                                          // Start-Menu/menu-accelerator-cue side-effects of
                                          // Win-drag/Alt-drag; see the big comment above
                                          // LowLevelKeyboardProc for why.
static DWORD  g_lastClickTime = 0;
static POINT  g_lastClickPos  = {0, 0};
static int    g_dbclWidth     = 0;
static int    g_dbclHeight    = 0;
static const DWORD DOUBLECLICK_THRESHOLD = 450;
static NOTIFYICONDATAW g_nid; // kept around so WM_DESTROY can NIM_DELETE it
static HINSTANCE g_hInstance        = NULL;
static UINT       g_wmTaskbarCreated = 0;

// On by default (they're the features being requested), but persisted in
// the registry rather than just an in-memory flag: this program is
// often launched via the auto-start task at every logon, and if a user
// turns one of these off because it's interfering with some other app,
// that choice should still be in effect after the next reboot - not
// silently reset back to "on" and surprise them again.
static bool g_altDragEnabled = true;
static bool g_winDragEnabled = true;

// Set by LowLevelMouseProc's WM_LBUTTONDOWN handler when a drag was
// triggered via the Win-key or Alt-key gesture respectively (not the
// plain double-click one) - read and cleared by LowLevelKeyboardProc(),
// see the comment above it for the full story. Two independent flags,
// not one shared - Win-drag and Alt-drag are mutually exclusive within
// a single WM_LBUTTONDOWN (see the if/else chain there), but keeping
// them separate means a future change to that exclusivity can't
// accidentally cross-suppress the wrong key's release.
static bool g_winKeyUsedForDrag = false;
static bool g_altKeyUsedForDrag = false;

// Active drag/resize session state (valid only while g_dragMode != DRAG_NONE)
static DragMode g_dragMode = DRAG_NONE;
static HWND     g_dragHwnd = NULL;
static int      g_offX = 0, g_offY = 0;                   // move: cursor offset from window origin
static RECT     g_origRect;                               // resize: window rect at grab time
static bool     g_resRight = false, g_resBottom = false;   // resize: which corner is being dragged
static int      g_resizeOffX = 0, g_resizeOffY = 0;        // resize: cursor offset from the moving edge(s)

// Resize-rate throttle state.
// Without throttling, every WM_MOUSEMOVE triggers one SetWindowPos call,
// which forces the target window through WM_SIZE -> layout -> WM_PAINT.
// A 1000 Hz gaming mouse produces 1000 such cycles per second; a 60 Hz
// display can show at most 60. The excess cycles do no visible work and
// just thrash the target window's UI thread - the primary cause of stutter
// that persists even after the WM_ENTERSIZEMOVE optimization.
// The throttle caps SetWindowPos calls to one per frame interval.
// g_pendingPt always holds the latest unprocessed cursor position so that
// WM_LBUTTONUP can apply it as the exact final size even if it was skipped.
static LARGE_INTEGER g_qpcFreq       = {0};   // QPC ticks/sec, set once in WinMain
static LONGLONG      g_lastResizeQpc = 0;     // QPC tick of the last SetWindowPos call
static POINT         g_pendingPt     = {0,0}; // latest cursor pos from WM_MOUSEMOVE
static bool          g_hasPendingPt  = false; // true when g_pendingPt has not yet been applied

// Last rect actually sent to SetWindowPos during the current resize
// session - lets ApplyResize() skip a call entirely when the new rect is
// identical to the last one sent (see the comment in ApplyResize() for
// why this happens routinely, not just in rare edge cases). Reset by
// StartResize() at the start of every session; g_resLastSentValid==false
// means "nothing sent yet this session", same pattern as g_lastResizeQpc==0
// above for the time-based throttle - these two checks are independent
// and compose: the QPC throttle limits how often ApplyResize() is even
// called, this one limits how often a call that *does* go through must
// actually touch SetWindowPos.
static int  g_resLastSentX = 0, g_resLastSentY = 0;
static int  g_resLastSentW = 0, g_resLastSentH = 0;
static bool g_resLastSentValid = false;

// ---------------------------------------------------------------------
// Core drag/resize logic.
//
// All of this exists *instead of* the Windows-delegated approach
// described in DECISION 2 above - keep that context in mind when
// reading StartMove/UpdateMove/StartResize/UpdateResize: every
// SetWindowPos call here is doing, by hand, exactly what we explicitly
// chose not to ask DefWindowProc to do for us.
// ---------------------------------------------------------------------

// Returns the top-level, draggable window at pt, or NULL if pt is over
// the taskbar / desktop / a maximized window.
//
// Maximized windows specifically: there's no edge or corner to
// visually grab (the window fills the work area), so a click anywhere
// inside one reads as "click on the application's own content", not
// "click meant for SmoothDrag" - unlike a normal window, where a user
// reaching for an edge has some visual sense of approaching one. Acting
// on Ctrl/Alt/double-click here would mean second-guessing clicks that
// are overwhelmingly more likely meant for whatever's underneath.
static HWND GetDraggableWindowAt(POINT pt) {
    HWND hwnd = GetAncestor(WindowFromPoint(pt), GA_ROOT);
    if (!hwnd || !IsWindow(hwnd) || !IsWindowVisible(hwnd)) return NULL;

    char className[256];
    if (!GetClassNameA(hwnd, className, sizeof(className))) return NULL;

    if (lstrcmpiA(className, "Shell_TrayWnd") == 0 ||
        lstrcmpiA(className, "Progman") == 0 ||
        lstrcmpiA(className, "WorkerW") == 0) {
        return NULL;
    }

    WINDOWPLACEMENT wp = {0};
    wp.length = sizeof(wp);
    GetWindowPlacement(hwnd, &wp);
    if (wp.showCmd == SW_SHOWMAXIMIZED) return NULL;

    return hwnd;
}

// ---------------------------------------------------------------------
// Native title-bar/resize-border conflict detection
//
// Bug this fixes: our hook sees every click system-wide *before*
// Windows itself decides whether that click landed on a window's
// client area or its non-client area (title bar / sizing border). If
// a double-click/Alt-drag/Win-drag move, or a Ctrl-resize, happens to
// land exactly on a window's real title bar or real resize border, TWO
// things start dragging the same window at once: our own tracking
// below, AND Windows' own native drag-the-caption/resize-the-border
// loop inside DefWindowProc - which reacts to that same
// WM_NCLBUTTONDOWN regardless of Ctrl/Alt/Win being held, because
// DefWindowProc never checks for those modifiers before starting its
// own loop. Two independent loops moving/resizing the same window from
// slightly different starting assumptions is exactly what produces the
// flicker/jitter reported when grabbing a real title bar.
//
// Fix: ask the window itself, via WM_NCHITTEST, what *it* thinks is at
// this point, and step aside if the answer is the same kind of native
// gesture we're about to start - HTCAPTION for move, one of the eight
// border codes for resize. SendMessageTimeout rather than SendMessage:
// this is a single one-time geometry query made once at the very start
// of a gesture (never per-WM_MOUSEMOVE, unlike SetWindowPos - see
// DECISION 2's CAVEAT for why that distinction matters), so even a
// slow answer only delays a gesture's *start* slightly rather than
// causing the per-frame backlog problem documented there; bounding it
// anyway costs nothing. On timeout/failure we assume no conflict and
// proceed exactly as before this fix existed - this can only ever make
// us defer to native *more* cautiously, never less.
//
// Deliberately narrow: only HTCAPTION/border codes count as a
// conflict, nothing else (e.g. HTSYSMENU, the system-menu icon at the
// caption's left edge, is its own separate native gesture - opens the
// system menu on click rather than starting a move-loop - and isn't
// handled here; not reported as a problem in practice, so left alone
// rather than guessed at).
static bool IsOnNativeCaption(HWND hwnd, POINT pt) {
    DWORD_PTR result = 0;
    if (!SendMessageTimeout(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y),
                             SMTO_ABORTIFHUNG, 50, &result)) {
        return false;
    }
    return (LRESULT)result == HTCAPTION;
}

static bool IsOnNativeResizeBorder(HWND hwnd, POINT pt) {
    DWORD_PTR result = 0;
    if (!SendMessageTimeout(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y),
                             SMTO_ABORTIFHUNG, 50, &result)) {
        return false;
    }
    switch ((LRESULT)result) {
        case HTLEFT: case HTRIGHT: case HTTOP: case HTBOTTOM:
        case HTTOPLEFT: case HTTOPRIGHT: case HTBOTTOMLEFT: case HTBOTTOMRIGHT:
            return true;
        default:
            return false;
    }
}
// ---------------------------------------------------------------------

// Remembers the cursor's offset from the window's top-left corner, so
// every later UpdateMove() call can reposition the window while keeping
// the cursor at the same relative spot inside it - the same arithmetic
// a native title-bar drag preserves, just computed by us instead of
// DefWindowProc.
//
// Deliberately has none of resize's WM_ENTERSIZEMOVE pairing, throttle,
// or dedup-guard machinery (see StartResize/UpdateResize/ApplyResize
// below for all three) - a move only ever costs the target window a
// WM_MOVE, not a WM_SIZE -> layout -> WM_PAINT cycle. WM_MOVE doesn't
// force most windows to recompute and redraw their internal layout the
// way WM_SIZE does, so there's no equivalent of resize's per-frame
// layout-thrashing stutter here for those mechanisms to fix.
static void StartMove(HWND hwnd, POINT pt) {
    RECT rc;
    GetWindowRect(hwnd, &rc);
    g_dragMode = DRAG_MOVE;
    g_dragHwnd = hwnd;
    g_offX = pt.x - rc.left;
    g_offY = pt.y - rc.top;
}

// Decides which corner is being dragged from the click position relative
// to the window's midpoint (e.g. a click in the bottom-right quadrant
// drags the bottom-right corner, keeping top-left fixed), and records
// the cursor's offset from the *moving* edges so UpdateResize() can
// keep tracking it precisely as the cursor moves.
static void StartResize(HWND hwnd, POINT pt) {
    GetWindowRect(hwnd, &g_origRect);
    g_dragMode  = DRAG_RESIZE;
    g_dragHwnd  = hwnd;
    g_resRight  = pt.x > (g_origRect.left + (g_origRect.right - g_origRect.left) / 2);
    g_resBottom = pt.y > (g_origRect.top + (g_origRect.bottom - g_origRect.top) / 2);
    g_resizeOffX = g_resRight ? (g_origRect.right - pt.x) : (g_origRect.left - pt.x);
    g_resizeOffY = g_resBottom ? (g_origRect.bottom - pt.y) : (g_origRect.top - pt.y);

    // ---- throttle: reset for new resize session ----
    // Allow the very first WM_MOUSEMOVE of this session to fire immediately
    // (g_lastResizeQpc == 0 bypasses the elapsed-time check in ApplyResize).
    // Pre-seed g_pendingPt with the click position so that a WM_LBUTTONUP
    // arriving before any WM_MOUSEMOVE still has a valid final position.
    g_lastResizeQpc = 0;
    g_pendingPt     = pt;
    g_hasPendingPt  = false;
    g_resLastSentValid = false;   // no rect sent yet this session - see ApplyResize()

    // ---- stutter fix (start) ----
    // Notify the target window that a size/move operation is beginning.
    // Two complementary effects:
    //
    // (a) Application-level: some windows handle WM_ENTERSIZEMOVE in their
    //     own WndProc to throttle expensive layout/paint work during resize
    //     (media players pausing rendering, shell hosts reducing redraws,
    //     etc.). Injecting this message lets them apply those optimizations
    //     even though the drag was not initiated by Windows' own title-bar
    //     machinery.
    //
    // (b) DWM-level: on Windows 10/11, where DWM composition is always
    //     active, WM_ENTERSIZEMOVE signals the compositor to enter its
    //     "live resize" path - a more efficient compositing mode for windows
    //     being continuously resized. This is the primary source of stutter
    //     reduction for modern windows whose content is rendered off-screen
    //     by DWM rather than by the application thread directly.
    //
    // Why SendNotifyMessage and not SendMessage:
    //   We are running inside a WH_MOUSE_LL hook callback on our own thread.
    //   SendMessage to a window on a *different* thread blocks our thread
    //   until the target processes the message, stalling the entire OS
    //   hook-dispatch chain for every other mouse event in the system.
    //   SendNotifyMessage delivers the message asynchronously across thread
    //   boundaries and returns immediately - safe to call from a hook.
    //
    // Why IsWindow():
    //   GetDraggableWindowAt() verified this HWND moments ago, but a tight
    //   race (the app exits between those two calls) could leave us with a
    //   stale handle. SendNotifyMessage to a destroyed HWND returns FALSE
    //   silently, so the check is not strictly required for safety, but it
    //   is cheap and documents the intent. Note: pairing is important -
    //   WM_EXITSIZEMOVE is sent on WM_LBUTTONUP (below) only when the window
    //   is still alive; if it closed mid-drag UpdateResize() already set
    //   g_dragMode to DRAG_NONE, so the WM_LBUTTONUP branch is skipped -
    //   which is correct because there is no window left to notify.
    if (IsWindow(g_dragHwnd)) {
        SendNotifyMessage(g_dragHwnd, WM_ENTERSIZEMOVE, 0, 0);
    }
    // ---- stutter fix (end) ----
}

static void UpdateMove(POINT pt) {
    if (!IsWindow(g_dragHwnd)) { g_dragMode = DRAG_NONE; return; }
    // Deliberately NOT using SWP_ASYNCWINDOWPOS here - see the CAVEAT
    // paragraph in DECISION 2 above (top of file) for why a plain
    // blocking call, despite the in-theory hook-timeout risk, is the
    // correct choice: it's what keeps this call's own rate naturally
    // capped at whatever the target window can actually keep up with.
    SetWindowPos(g_dragHwnd, NULL, pt.x - g_offX, pt.y - g_offY, 0, 0,
                 SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}

// Core resize math and SetWindowPos, factored out so it can be called
// from both the throttled UpdateResize path (on every allowed WM_MOUSEMOVE)
// and directly from WM_LBUTTONUP (to flush any final throttled position so
// the window lands exactly where the cursor was when the button was released).
static void ApplyResize(POINT pt) {
    if (!IsWindow(g_dragHwnd)) { g_dragMode = DRAG_NONE; return; }
    const RECT *rc = &g_origRect;
    int nW, nH, nX, nY;

    // 120px floor on both axes - without one, dragging the cursor past
    // the window's opposite edge would compute a zero or negative size.
    // Most apps handle that badly (collapsed/invisible content), and
    // SetWindowPos itself isn't guaranteed to do anything sensible with
    // it either. 120 is just a reasonable floor, not a value with any
    // special meaning - the point is only that *some* floor exists.
    if (g_resRight) {
        nW = (pt.x + g_resizeOffX) - rc->left;
        if (nW < 120) nW = 120;
        nX = rc->left;                // left edge is the fixed anchor here - never moves
    } else {
        nW = rc->right - (pt.x + g_resizeOffX);
        if (nW < 120) nW = 120;
        nX = rc->right - nW;          // re-derive position from the (possibly clamped) width
    }
    if (g_resBottom) {
        nH = (pt.y + g_resizeOffY) - rc->top;
        if (nH < 120) nH = 120;
        nY = rc->top;                 // top edge is the fixed anchor here - never moves
    } else {
        nH = rc->bottom - (pt.y + g_resizeOffY);
        if (nH < 120) nH = 120;
        nY = rc->bottom - nH;         // re-derive position from the (possibly clamped) height
    }

    // Skip the call entirely if it would send the exact same rect we
    // already sent last time this session. This is not a rare edge
    // case: it happens for *every* WM_MOUSEMOVE where the cursor is past
    // the 120px minimum-size clamp on an axis - many different cursor
    // positions all map to the same clamped nW/nH, so without this check
    // every one of them would still issue a full SetWindowPos to the
    // target window for a rect it is already at. A plain integer
    // comparison costs nothing when the rect actually did change, so
    // this only ever removes pointless work, never delays a real resize
    // step - and unlike SWP_ASYNCWINDOWPOS (see the CAVEAT in DECISION 2
    // above for why that one was reverted), skipping a call here can
    // only ever reduce the number of SetWindowPos calls made, never let
    // them get ahead of what the target has actually processed - so it
    // can't reintroduce the same backlog problem.
    if (g_resLastSentValid &&
        nX == g_resLastSentX && nY == g_resLastSentY &&
        nW == g_resLastSentW && nH == g_resLastSentH) {
        return;
    }

    // Deliberately NOT using SWP_ASYNCWINDOWPOS here - see the CAVEAT
    // paragraph in DECISION 2 above (top of file). A plain blocking call
    // is what keeps this naturally rate-limited to whatever the target
    // window can actually keep up with; posting it asynchronously let
    // requests pile up faster than a slow window could drain them,
    // which is worse than the stutter it was meant to fix.
    SetWindowPos(g_dragHwnd, NULL, nX, nY, nW, nH,
                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);

    g_resLastSentX = nX; g_resLastSentY = nY;
    g_resLastSentW = nW; g_resLastSentH = nH;
    g_resLastSentValid = true;
}

// Throttled wrapper around ApplyResize.
//
// Root cause of the remaining stutter after the WM_ENTERSIZEMOVE fix:
// a 1000 Hz gaming mouse delivers 1000 WM_MOUSEMOVE events per second,
// each of which previously triggered one SetWindowPos -> WM_SIZE -> layout
// -> WM_PAINT cycle in the target window. The DWM can display at most
// 60-144 frames per second, so the vast majority of those cycles produce
// frames that never appear on screen. They just saturate the target's UI
// thread with layout work, causing visible stutter on complex windows like
// the Save As dialog. Native Windows resize avoids this because its internal
// modal loop is implicitly synchronized to DWM vblank - it never issues more
// SetWindowPos calls than the display can show.
//
// Fix: throttle to one SetWindowPos per ~16 ms (~62 fps). Calls that arrive
// within 16 ms of the previous one are dropped; g_pendingPt always holds the
// latest cursor position so that the final position at mouse release is never
// lost (see the WM_LBUTTONUP handler below).
//
// Why 16 ms: conservative baseline that matches 60 Hz. At 120 Hz a value
// of 8 ms would be tighter, but 16 ms is safe for all refresh rates - on
// a 120 Hz display the DWM composites every 8 ms anyway, so even though we
// send one frame per 16 ms the DWM still shows it in the very next vblank.
// The perceptible lag difference from 60->120 Hz throttle is negligible for
// resize (vs. cursor movement, where it matters more).
static void UpdateResize(POINT pt) {
    if (!IsWindow(g_dragHwnd)) { g_dragMode = DRAG_NONE; return; }

    // Always record the latest position so WM_LBUTTONUP can flush it.
    g_pendingPt    = pt;
    g_hasPendingPt = true;

    // Throttle check: skip if within 16 ms of the last applied resize.
    // g_lastResizeQpc == 0 means "first call in this session" - always fires.
    if (g_qpcFreq.QuadPart > 0 && g_lastResizeQpc != 0) {
        LARGE_INTEGER now;
        QueryPerformanceCounter(&now);
        LONGLONG elapsedUs = (now.QuadPart - g_lastResizeQpc) * 1000000
                             / g_qpcFreq.QuadPart;
        if (elapsedUs < 16000) return;   // 16 ms = ~62 fps ceiling
    }

    // This call is allowed through - update the timestamp and apply.
    {
        LARGE_INTEGER now;
        QueryPerformanceCounter(&now);
        g_lastResizeQpc = now.QuadPart;
    }
    g_hasPendingPt = false;
    ApplyResize(pt);
}

// The low-level mouse hook callback. Windows invokes this synchronously,
// on our own thread, for every mouse event system-wide - so everything
// in here has to be fast. That's exactly why we call SetWindowPos
// directly instead of delegating to DefWindowProc (see DECISION 2
// above): in the overwhelming majority of cases it returns in
// microseconds, fast enough to do the actual move/resize tracking right
// here on every WM_MOUSEMOVE. (The rejected WM_NCLBUTTONDOWN-delegation
// approach would NOT have been safe to call from here at all - that
// call blocks for the *entire* duration of the drag, no matter how fast
// the target window is.) The CAVEAT in DECISION 2 covers the one case
// where even SetWindowPos can still block for a while - an unusually
// slow target window - and why we accept that risk here rather than
// the alternative (which has its own, worse, failure mode).
static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        MSLLHOOKSTRUCT *info = (MSLLHOOKSTRUCT *)lParam;
        POINT pt = info->pt;

        switch (wParam) {
        case WM_LBUTTONDOWN: {
            if (g_dragMode != DRAG_NONE) break;  // a drag is already active, ignore

            bool ctrlPressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
            bool altPressed  = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; // VK_MENU = Alt
            bool winPressed  = (GetAsyncKeyState(VK_LWIN) & 0x8000) != 0 ||
                                (GetAsyncKeyState(VK_RWIN) & 0x8000) != 0;
            HWND hwnd = GetDraggableWindowAt(pt);
            if (!hwnd) break;

            if (ctrlPressed) {
                // Ctrl+click resizes immediately on a single click - no
                // double-click gesture needed, since holding Ctrl is
                // already a deliberate signal. This branch never touches
                // g_lastClickTime/g_lastClickPos, so it doesn't interfere
                // with double-click tracking for plain clicks either.
                //
                // Skipped if this point is already a native resize
                // border per IsOnNativeResizeBorder() above - Windows'
                // own resize-drag will react to this exact click on its
                // own regardless of Ctrl being held, so starting our
                // tracking too would double up on the same gesture.
                if (!IsOnNativeResizeBorder(hwnd, pt)) {
                    StartResize(hwnd, pt);
                }
            } else if (winPressed && g_winDragEnabled) {
                // Win+drag - see the big comment above LowLevelKeyboardProc
                // for the Start-Menu-suppression half of this feature.
                // Checked before Alt below: if both happen to be held,
                // Win wins, simply because it's the gesture with the extra
                // keyboard-hook machinery behind it and therefore the more
                // "deliberate" one to honor first - not a meaningful
                // real-world scenario either way.
                //
                // g_winKeyUsedForDrag is set unconditionally, BEFORE the
                // native-caption check - this is the one thing that must
                // NOT be gated on it. The flag means "Win was used as a
                // drag modifier", which is true the moment the user does
                // this regardless of whether OUR code or Windows' own
                // title-bar drag loop ends up actually moving the window;
                // the keyboard hook needs to suppress this Win release
                // either way (a native-handled title-bar drag doesn't
                // suppress it on its own - that's only ever done by our
                // keyboard hook, see LowLevelKeyboardProc). Only StartMove
                // itself - our own tracking - should be skipped on a real
                // title bar; gating the flag the same way was the actual
                // bug that let the Start Menu reappear specifically when
                // grabbing the title bar with Win held.
                g_winKeyUsedForDrag = true;
                if (!IsOnNativeCaption(hwnd, pt)) {
                    StartMove(hwnd, pt);
                }
            } else if (altPressed && g_altDragEnabled) {
                // Linux window-manager-style Alt+drag: a single click and
                // hold anywhere in the window starts a move immediately,
                // same as Ctrl-resize above - no double-click needed.
                // Reuses StartMove/UpdateMove exactly as-is; the only
                // thing that differs from the double-click gesture below
                // is how the gesture is *recognized*, not how the move
                // itself is performed.
                //
                // This is gated on g_altDragEnabled (toggled from the
                // tray menu, default on) because several graphics/3D/CAD
                // applications already use Alt+drag themselves for their
                // own tools (camera orbit, pan, brush size, etc.) - a
                // global hook reacting to the same combination would
                // otherwise hijack that gesture away from those apps with
                // no way to tell them apart from inside the hook. Letting
                // the user turn it off entirely, rather than e.g. trying
                // to special-case specific known applications by class
                // name, is the only approach that reliably avoids that
                // conflict for *every* such application, known or not.
                //
                // g_altKeyUsedForDrag is set unconditionally, BEFORE the
                // native-caption check, for the exact same reason as
                // g_winKeyUsedForDrag above: it means "Alt was used as a
                // drag modifier", true regardless of whether OUR code or
                // Windows' own title-bar drag loop ends up actually
                // moving the window. Only StartMove itself should be
                // skipped on a real title bar - gating the flag the same
                // way was the actual bug that let the menu-accelerator-
                // cues flash reappear specifically when grabbing the
                // title bar with Alt held. LowLevelKeyboardProc reads
                // this flag to suppress that flash on Alt's release - see
                // the big comment above that function.
                g_altKeyUsedForDrag = true;
                if (!IsOnNativeCaption(hwnd, pt)) {
                    StartMove(hwnd, pt);
                }
            } else {
                DWORD now = GetTickCount();
                bool isDoubleClick =
                    (now - g_lastClickTime < DOUBLECLICK_THRESHOLD &&
                     abs(pt.x - g_lastClickPos.x) < g_dbclWidth &&
                     abs(pt.y - g_lastClickPos.y) < g_dbclHeight);

                if (isDoubleClick) {
                    // Same native-caption check as the modifier-driven
                    // gestures above - the consume-the-pair logic just
                    // below still always runs regardless, so a third
                    // click right after a deferred-to-native drag is
                    // still correctly read as a fresh first click.
                    if (!IsOnNativeCaption(hwnd, pt)) {
                        StartMove(hwnd, pt);
                    }
                    // Consume the pair instead of leaving g_lastClickTime
                    // at the second click's timestamp. Without this, a
                    // fast third click landing near the same spot within
                    // DOUBLECLICK_THRESHOLD of a just-finished drag could
                    // be misread as the second half of a brand-new
                    // double-click against this now-stale timestamp,
                    // starting an unintended drag the user never asked
                    // for. Zeroing it out means the next click is always
                    // evaluated as the *first* click of a fresh pair.
                    g_lastClickTime = 0;
                    g_lastClickPos.x = 0;
                    g_lastClickPos.y = 0;
                } else {
                    g_lastClickTime = now;
                    g_lastClickPos  = pt;
                }
            }
            break;
        }
        case WM_MOUSEMOVE:
            if (g_dragMode == DRAG_MOVE)        UpdateMove(pt);
            else if (g_dragMode == DRAG_RESIZE) UpdateResize(pt);
            break;

        case WM_LBUTTONUP:
            if (g_dragMode == DRAG_RESIZE && IsWindow(g_dragHwnd)) {
                // ---- throttle: flush final position ----
                // If the last WM_MOUSEMOVE was dropped by the 16 ms throttle,
                // g_hasPendingPt is true and g_pendingPt holds the cursor's
                // actual release position. Apply it now so the window's final
                // size is exactly where the user let go, not where the last
                // un-throttled call happened to land (potentially 16 ms earlier).
                if (g_hasPendingPt) {
                    ApplyResize(g_pendingPt);
                    g_hasPendingPt = false;
                }
                // ---- stutter fix (start) ----
                // Symmetric counterpart to the WM_ENTERSIZEMOVE sent in
                // StartResize: tell the window (and the DWM compositor) that
                // the resize operation has ended so they can exit their
                // respective optimized-resize modes and resume full-quality
                // rendering.
                // Same SendNotifyMessage rationale as in StartResize: async
                // cross-thread delivery keeps the hook callback returning fast.
                SendNotifyMessage(g_dragHwnd, WM_EXITSIZEMOVE, 0, 0);

                // Force a complete repaint of the window at its final size.
                // During resize, WM_ENTERSIZEMOVE may have caused the window
                // or DWM to defer or skip intermediate updates; this ensures
                // every pixel is redrawn cleanly once the mouse button is
                // released, so the window never appears "dirty" after a drag.
                InvalidateRect(g_dragHwnd, NULL, TRUE);
                // ---- stutter fix (end) ----
            }
            g_dragMode = DRAG_NONE;
            g_dragHwnd = NULL;
            break;
        }
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// ---------------------------------------------------------------------
// Win/Alt-key lone-tap suppression
//
// Problem: when the Win key or Alt key is used as a custom drag
// modifier (see winPressed/altPressed in LowLevelMouseProc above), its
// eventual release still reaches Windows' own lone-modifier-tap
// detection afterward - we only *read* the key state in the mouse
// hook, we never consume the keystroke itself. As far as Windows can
// tell, the key was pressed and released with nothing else happening
// in between (a mouse click during the hold doesn't count as "something
// else" to either of these two detectors) - exactly the trigger
// condition both of the following react to:
//   - Win alone: opens the Start Menu (the same reason a real Win+E or
//     Win+R does NOT open it - Win was used together with another key).
//   - Alt alone: toggles keyboard menu-accelerator cues (the underlined
//     letters that flash onto a window's menu bar) - the same reason
//     Alt+Tab or Alt+F4 don't trigger that flash.
// Left alone, every single Win-drag would end with the Start Menu
// popping open on release, and every single Alt-drag would end with a
// brief, distracting accelerator-cue flash on whatever window has
// focus.
//
// Fix, identical in shape for both keys: make the key's eventual
// release look like "used together with another key" instead of
// "tapped alone", even though no other real key was involved:
//   1. Suppress the *physical* WM_KEYUP/WM_SYSKEYUP of the key (return
//      1 below), so Windows never sees that release in its original,
//      lone form at all.
//   2. Inject a harmless key press+release in between, via SendInput -
//      this is what makes the still-logically-held key register as
//      "used with another key" before it's ever released.
//   3. Inject a synthetic release of the real key immediately after,
//      so its state is still correctly cleared system-wide - just no
//      longer as a *lone* press+release.
// SuppressLoneModifierTap() below does steps 2-3 for whichever key
// asks; the caller (Win or Alt branch) is only responsible for step 1
// (the return value) and for clearing its own "was this used for a
// drag" flag first.
//
// Why VK 0xFF specifically: it just needs to be a code with no key
// physically wired to it, so nothing else happens when it's "pressed".
// 0xFF satisfies that (Microsoft's own Winuser.h virtual-key table only
// assigns codes 0x01-0xFE; 0xFF falls outside it entirely). It is NOT
// VK_F24 - VK_F24 is a real, separately-named constant, 0x87. An
// earlier version of this comment called 0xFF "VK_F24"; that was
// simply wrong, not a stylistic shorthand - they are two unrelated
// values, and nothing here actually depends on F24 specifically, only
// on the code being safely unused.
//
// Why all 3 events go through one SendInput call rather than three
// separate calls: SendInput delivers its whole array to the input
// subsystem as a single atomic unit, guaranteeing nothing real can be
// interleaved between the dummy key and the synthetic release. Three
// separate calls would reopen exactly the gap this trick depends on
// staying closed.
//
// Known limitation: this only fires for VK_LMENU/VK_RMENU and
// VK_LWIN/VK_RWIN specifically, matching what this low-level hook has
// actually been observed to report for those two keys. Some non-US/UK
// keyboard layouts report AltGr as a Ctrl+Alt combination rather than
// a plain VK_RMENU, which this wouldn't catch - not fixed here since
// it hasn't been seen in practice and guessing at unverified layout
// behavior risks a worse bug than the one it would fix.
// ---------------------------------------------------------------------

// Performs steps 2-3 described above for vkCode (the real modifier key
// being released, with `extended` carrying over its LLKHF_EXTENDED
// state so the synthetic release reports the correct left/right key -
// without it, e.g. a right-Win or right-Alt release would be reported
// as the left one, and any right-key-specific shortcut elsewhere on
// the system could misfire on this synthetic event). Always returns 1,
// the value both callers below need to return from the hook to
// suppress the key's original physical release.
static LRESULT SuppressLoneModifierTap(WORD vkCode, bool extended) {
    INPUT inputs[3] = {0};

    // Inert key down, then up - see the comment above for why 0xFF and
    // why this pair makes the real key register as "used with another
    // key" rather than tapped alone.
    inputs[0].type   = INPUT_KEYBOARD;
    inputs[0].ki.wVk = 0xFF;

    inputs[1].type      = INPUT_KEYBOARD;
    inputs[1].ki.wVk    = 0xFF;
    inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;

    // The real key's release, synthesized now that the physical one is
    // being suppressed by the caller's return 1.
    inputs[2].type   = INPUT_KEYBOARD;
    inputs[2].ki.wVk = vkCode;
    inputs[2].ki.dwFlags = KEYEVENTF_KEYUP | (extended ? KEYEVENTF_EXTENDEDKEY : 0);

    SendInput(3, inputs, sizeof(INPUT));
    return 1;
}

static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT *info = (KBDLLHOOKSTRUCT *)lParam;
        bool extended = (info->flags & LLKHF_EXTENDED) != 0;

        if (info->vkCode == VK_LWIN || info->vkCode == VK_RWIN) {
            if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
                // Every fresh Win keydown resets this: g_winKeyUsedForDrag
                // only becomes true if THIS particular press goes on to
                // actually start a Win-drag (set in LowLevelMouseProc's
                // WM_LBUTTONDOWN handler above) - so a plain tap with no
                // drag involved reaches WM_KEYUP below with the flag
                // still false, and is let through untouched.
                g_winKeyUsedForDrag = false;
            }
            else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
                if (g_winKeyUsedForDrag) {
                    // Clear the flag *before* injecting anything: the
                    // synthetic Win-up inside SuppressLoneModifierTap()
                    // re-enters this same function, and if the flag
                    // were still true at that point we'd run this whole
                    // injection again on our own injected event - an
                    // infinite loop.
                    g_winKeyUsedForDrag = false;
                    return SuppressLoneModifierTap((WORD)info->vkCode, extended);
                }
            }
        }
        else if (info->vkCode == VK_LMENU || info->vkCode == VK_RMENU) {
            // Mirrors the Win-key block above exactly - see the big
            // comment before LowLevelKeyboardProc for what this
            // prevents and why the mechanism is identical for both keys.
            if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
                g_altKeyUsedForDrag = false;
            }
            else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
                if (g_altKeyUsedForDrag) {
                    g_altKeyUsedForDrag = false;
                    return SuppressLoneModifierTap((WORD)info->vkCode, extended);
                }
            }
        }
    }
    return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}

// ---------------------------------------------------------------------
// Auto-start (Task Scheduler) management
//
// We use schtasks.exe + a generated XML task definition rather than the
// Task Scheduler COM API (ITaskService/ITaskDefinition/...) directly.
// The COM route avoids spawning a child process, but in plain C it means
// hand-rolling vtable-style interface calls, BSTR allocation/cleanup,
// and CoInitialize/CoUninitialize bookkeeping for a one-shot, rarely
// used action (the user clicks this menu item once, not in a hot path).
// schtasks.exe is a standard, always-present Windows tool whose XML
// input format exposes every setting we need (in particular Priority
// and ExecutionTimeLimit, below) - simpler to write, simpler to verify
// by eye, and simpler to debug (the generated XML can just be opened
// and read) than the equivalent COM interop would be.
// ---------------------------------------------------------------------

// Full path to a tool in %SystemRoot%\System32, e.g. "schtasks.exe".
// Deliberately NOT invoked by bare name: CreateProcess's search order
// checks the calling EXE's own folder *before* System32, so if this
// program is ever run from a folder a less trusted party could write
// to, a planted "schtasks.exe" sitting next to it would silently run
// instead of the real one. The full path removes that ambiguity.
static bool GetSystemToolPath(wchar_t *outPath, size_t outCapacity, const wchar_t *toolFileName) {
    UINT n = GetSystemDirectoryW(outPath, (UINT)outCapacity);
    if (n == 0 || n >= outCapacity) return false;
    size_t len = wcslen(outPath);
    if (len + 1 + wcslen(toolFileName) + 1 > outCapacity) return false;
    outPath[len] = L'\\';
    wcscpy(outPath + len + 1, toolFileName);
    return true;
}

// Runs a command line synchronously with no visible console window and
// returns its exit code (or (DWORD)-1 if it couldn't even be started or
// didn't finish within timeoutMs). CREATE_NO_WINDOW matters here:
// schtasks.exe is a console application, and launching a console app
// from a -mwindows (GUI subsystem) process without this flag makes a
// console window briefly flash on screen every time the user toggles
// the autostart menu item.
//
// timeoutMs is deliberately a parameter rather than one fixed constant:
// this same helper is used both for the user-triggered toggle (where
// waiting a bit longer for a definitive yes/no before showing the
// confirmation MessageBox is reasonable) and for the silent one-time
// startup migration cleanup below, which must NOT be allowed to block
// the rest of WinMain - and therefore the hook/tray icon becoming
// responsive - for very long if schtasks.exe is ever slow to respond.
static DWORD RunHidden(wchar_t *mutableCmdLine, DWORD timeoutMs) {
    STARTUPINFOW si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si);
    PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi));
    DWORD exitCode = (DWORD)-1;
    if (CreateProcessW(NULL, mutableCmdLine, NULL, NULL, FALSE,
                        CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
        if (WaitForSingleObject(pi.hProcess, timeoutMs) == WAIT_OBJECT_0) {
            GetExitCodeProcess(pi.hProcess, &exitCode);
        } else {
            TerminateProcess(pi.hProcess, 1); // didn't finish in time - don't leave it running
        }
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }
    return exitCode;
}

// schtasks /Query exits 0 if the task exists, non-zero otherwise - used
// both to decide the checkmark state when the tray menu is opened, and
// to decide which direction ToggleAutoStart() should go.
static bool IsAutoStartTaskRegistered(void) {
    wchar_t schtasksPath[MAX_PATH];
    if (!GetSystemToolPath(schtasksPath, MAX_PATH, L"schtasks.exe")) return false;

    wchar_t cmd[600];
    wcscpy(cmd, L"\"");
    wcscat(cmd, schtasksPath);
    wcscat(cmd, L"\" /Query /TN \"" AUTOSTART_TASK_NAME L"\"");

    return RunHidden(cmd, 5000) == 0; // 5s - this runs every time the tray menu opens
}

// Writes the Task Scheduler XML task definition. Two settings here
// exist specifically to close gaps that would otherwise bite a utility
// meant to run elevated, indefinitely, in the background:
//
//  - Priority: Task Scheduler's *default* priority value, used whenever
//    a task doesn't specify one, is 7 - which, per Microsoft's own
//    Task Scheduler schema documentation, maps to
//    BELOW_NORMAL_PRIORITY_CLASS, not NORMAL. Concretely:
//        Priority 0      -> REALTIME_PRIORITY_CLASS
//        Priority 1      -> HIGH_PRIORITY_CLASS
//        Priority 2-3    -> ABOVE_NORMAL_PRIORITY_CLASS
//        Priority 4-6    -> NORMAL_PRIORITY_CLASS
//        Priority 7-8 (*default*) -> BELOW_NORMAL_PRIORITY_CLASS
//        Priority 9-10   -> IDLE_PRIORITY_CLASS
//    We set Priority to 2 (ABOVE_NORMAL) below, matching what our own
//    WinMain already does via SetPriorityClass() - but the task-level
//    setting isn't just a redundant safety net. Per that same Microsoft
//    documentation, the Priority value also drives the process's I/O
//    priority and memory-priority hints, neither of which
//    SetPriorityClass() touches at all. So setting it correctly here is
//    strictly more complete than what our own runtime call can achieve
//    on its own.
//
//  - ExecutionTimeLimit: if left unset, Task Scheduler's documented
//    default is 3 days (PT72H) - after which it force-terminates the
//    task's process, even if it's still running perfectly fine. Found
//    this while implementing the feature: without explicitly setting
//    ExecutionTimeLimit to PT0S (no limit), an auto-started SmoothDrag
//    would get silently killed every three days of uptime.
static bool WriteTaskXmlFile(const wchar_t *xmlPath, const wchar_t *exePath) {
    wchar_t userDomain[256] = {0};
    wchar_t userName[256]   = {0};
    GetEnvironmentVariableW(L"USERDOMAIN", userDomain, 256);
    GetEnvironmentVariableW(L"USERNAME", userName, 256);

    wchar_t userId[520];
    wcscpy(userId, userDomain);
    wcscat(userId, L"\\");
    wcscat(userId, userName);

    // RunLevel=HighestAvailable + LogonType=InteractiveToken on a
    // LogonTrigger is Microsoft's own documented mechanism for elevated
    // auto-start without a UAC prompt at every logon: the decision to
    // run elevated is made once, here, at registration time (while this
    // program is itself already running elevated), and is stored with
    // the task; the Task Scheduler service - which already runs with
    // sufficient privilege - simply honors it on every subsequent logon
    // without asking again. This is not a UAC bypass/workaround; it's
    // the supported way elevated background tools are meant to launch
    // at startup.
    wchar_t xml[4096];
    wcscpy(xml,
        L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n"
        L"<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\r\n"
        L"  <RegistrationInfo>\r\n"
        L"    <Description>Launch SmoothDrag elevated at logon</Description>\r\n"
        L"  </RegistrationInfo>\r\n"
        L"  <Triggers>\r\n"
        L"    <LogonTrigger>\r\n"
        L"      <Enabled>true</Enabled>\r\n"
        // At logon, Task Scheduler fires the trigger before explorer.exe has
        // finished loading. Shell_NotifyIconW(NIM_ADD) will silently fail if
        // the shell is not yet ready, and with no delay the program may exit
        // before TaskbarCreated ever arrives to let it retry. A 30-second
        // delay gives explorer enough time to fully initialize before
        // SmoothDrag starts, eliminating that race without any code-level
        // polling or retry loop. This is also what allows the fix in WinMain
        // (non-fatal AddTrayIcon at startup) to be a true safety net rather
        // than the primary mechanism: in practice the shell should always be
        // ready by the time the delay expires.
        L"      <Delay>PT30S</Delay>\r\n"
        L"      <UserId>");
    wcscat(xml, userId);
    wcscat(xml,
        L"</UserId>\r\n"
        L"    </LogonTrigger>\r\n"
        L"  </Triggers>\r\n"
        L"  <Principals>\r\n"
        L"    <Principal id=\"Author\">\r\n"
        L"      <UserId>");
    wcscat(xml, userId);
    wcscat(xml,
        L"</UserId>\r\n"
        L"      <LogonType>InteractiveToken</LogonType>\r\n"
        L"      <RunLevel>HighestAvailable</RunLevel>\r\n"
        L"    </Principal>\r\n"
        L"  </Principals>\r\n"
        L"  <Settings>\r\n"
        L"    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\r\n"
        L"    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\r\n"
        L"    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\r\n"
        L"    <AllowHardTerminate>true</AllowHardTerminate>\r\n"
        L"    <StartWhenAvailable>true</StartWhenAvailable>\r\n"
        L"    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\r\n"
        L"    <Enabled>true</Enabled>\r\n"
        L"    <Hidden>false</Hidden>\r\n"
        L"    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\r\n"   // no 3-day default kill - see comment above
        L"    <Priority>2</Priority>\r\n"                         // ABOVE_NORMAL, not the 7/BELOW_NORMAL default - see comment above
        L"  </Settings>\r\n"
        L"  <Actions Context=\"Author\">\r\n"
        L"    <Exec>\r\n"
        L"      <Command>\"");
    wcscat(xml, exePath);
    wcscat(xml,
        L"\"</Command>\r\n"
        L"    </Exec>\r\n"
        L"  </Actions>\r\n"
        L"</Task>\r\n");

    HANDLE hFile = CreateFileW(xmlPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return false;

    WORD bom = 0xFEFF; // file must actually be UTF-16LE to match the XML's declared encoding
    DWORD written;
    bool ok = WriteFile(hFile, &bom, sizeof(bom), &written, NULL) &&
              WriteFile(hFile, xml, (DWORD)(wcslen(xml) * sizeof(wchar_t)), &written, NULL);
    CloseHandle(hFile);
    return ok;
}

// Builds the XML in a temp file (always rebuilt fresh from the EXE's
// *current* location, so if the EXE is ever moved, simply toggling
// autostart off and back on re-points the task automatically), then
// registers it with /F to overwrite any previous registration.
static bool CreateAutoStartTask(void) {
    wchar_t exePath[MAX_PATH];
    if (!GetModuleFileNameW(NULL, exePath, MAX_PATH)) return false;

    wchar_t tempDir[MAX_PATH];
    if (!GetTempPathW(MAX_PATH, tempDir)) return false;
    wchar_t xmlPath[MAX_PATH];
    wcscpy(xmlPath, tempDir);
    wcscat(xmlPath, L"SmoothDrag_task.xml");

    if (!WriteTaskXmlFile(xmlPath, exePath)) return false;

    wchar_t schtasksPath[MAX_PATH];
    if (!GetSystemToolPath(schtasksPath, MAX_PATH, L"schtasks.exe")) {
        DeleteFileW(xmlPath);
        return false;
    }

    wchar_t cmd[1200];
    wcscpy(cmd, L"\"");
    wcscat(cmd, schtasksPath);
    wcscat(cmd, L"\" /Create /TN \"" AUTOSTART_TASK_NAME L"\" /XML \"");
    wcscat(cmd, xmlPath);
    wcscat(cmd, L"\" /F");

    DWORD exitCode = RunHidden(cmd, 10000); // 10s - user-triggered, MessageBoxW below waits on this anyway
    DeleteFileW(xmlPath);
    return exitCode == 0;
}

static bool DeleteAutoStartTask(void) {
    wchar_t schtasksPath[MAX_PATH];
    if (!GetSystemToolPath(schtasksPath, MAX_PATH, L"schtasks.exe")) return false;

    wchar_t cmd[600];
    wcscpy(cmd, L"\"");
    wcscat(cmd, schtasksPath);
    wcscat(cmd, L"\" /Delete /TN \"" AUTOSTART_TASK_NAME L"\" /F");

    return RunHidden(cmd, 10000) == 0; // 10s - user-triggered, same reasoning as above
}

static void ToggleAutoStart(HWND hWnd, bool enable) {
    bool ok = enable ? CreateAutoStartTask() : DeleteAutoStartTask();
    MessageBoxW(hWnd,
        ok ? (enable ? L"הפעלה אוטומטית עם ווינדוס הופעלה בהצלחה."
                     : L"ההפעלה האוטומטית עם ווינדוס בוטלה.")
           : L"הפעולה נכשלה. ודא שהתוכנה רצה כמנהל ונסה שוב.",
        L"SmoothDrag",
        MB_OK | (ok ? MB_ICONINFORMATION : MB_ICONWARNING));
}

// ---------------------------------------------------------------------
// Alt-drag enabled/disabled persistence (HKCU, survives reboots/relaunches)
//
// HKEY_CURRENT_USER, not HKEY_LOCAL_MACHINE: this process runs elevated,
// but HKCU still correctly maps to the *same signed-in user's* hive when
// elevation happens via that user's own admin token (the normal case
// here) - no UIPI-style restriction applies to registry access the way
// it does to window messages, so this needs no special handling beyond
// just using HKCU normally.
// ---------------------------------------------------------------------

// Generic on/off-setting persistence by value name - both AltDragEnabled
// and WinDragEnabled use these, rather than duplicating the same handful
// of Reg* calls per setting.
static bool LoadBoolSetting(const wchar_t *valueName, bool defaultValue) {
    HKEY hKey;
    DWORD value = defaultValue ? 1 : 0;
    if (RegOpenKeyExW(HKEY_CURRENT_USER, SETTINGS_REGKEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        DWORD data = value, size = sizeof(data), type = 0;
        if (RegQueryValueExW(hKey, valueName, NULL, &type, (BYTE *)&data, &size) == ERROR_SUCCESS &&
            type == REG_DWORD) {
            value = data;
        }
        RegCloseKey(hKey);
    }
    return value != 0;
}

static void SaveBoolSetting(const wchar_t *valueName, bool enabled) {
    HKEY hKey;
    if (RegCreateKeyExW(HKEY_CURRENT_USER, SETTINGS_REGKEY, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
        DWORD data = enabled ? 1 : 0;
        RegSetValueExW(hKey, valueName, 0, REG_DWORD, (const BYTE *)&data, sizeof(data));
        RegCloseKey(hKey);
    }
}

// ---------------------------------------------------------------------
// Tray icon window
//
// Shell_NotifyIcon needs an HWND to deliver click notifications to, so
// this hidden (never shown) window exists purely to host that callback
// and to give the message loop in WinMain something legitimate to
// dispatch. It is never made visible and never appears in the taskbar
// or Alt-Tab.
// ---------------------------------------------------------------------

static bool AddTrayIcon(HWND hWnd, HINSTANCE hInstance); // defined below, used on WM_TASKBARCREATED too

static LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    // explorer.exe broadcasts this registered message to every top-level
    // window if it restarts (crash, manual restart, etc.) - the entire
    // taskbar, and our icon along with it, gets torn down and rebuilt,
    // so without this our icon would vanish permanently until SmoothDrag
    // itself is restarted.
    if (msg == g_wmTaskbarCreated && g_wmTaskbarCreated != 0) {
        AddTrayIcon(hWnd, g_hInstance);
        return 0;
    }

    switch (msg) {
    case WM_TRAYICON:
        if (lParam == WM_RBUTTONUP || lParam == WM_CONTEXTMENU) {
            POINT pt;
            GetCursorPos(&pt);

            bool autoStartOn = IsAutoStartTaskRegistered();

            HMENU hMenu = CreatePopupMenu();
            AppendMenuW(hMenu, MF_STRING | (autoStartOn ? MF_CHECKED : MF_UNCHECKED),
                        ID_TRAY_AUTOSTART, L"התחל עם ווינדוס");
            AppendMenuW(hMenu, MF_STRING | (g_winDragEnabled ? MF_CHECKED : MF_UNCHECKED),
                        ID_TRAY_WINDRAG, L"גרירה באמצעות Win");
            AppendMenuW(hMenu, MF_STRING | (g_altDragEnabled ? MF_CHECKED : MF_UNCHECKED),
                        ID_TRAY_ALTDRAG, L"גרירה באמצעות Alt");
            AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
            AppendMenuW(hMenu, MF_STRING, ID_TRAY_EXIT, L"יציאה");

            SetForegroundWindow(hWnd);
            int cmd = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
                                      pt.x, pt.y, 0, hWnd, NULL);
            DestroyMenu(hMenu);
            // Standard fix-up: without this, clicking away from the menu
            // sometimes fails to dismiss it properly.
            PostMessage(hWnd, WM_NULL, 0, 0);

            if (cmd == ID_TRAY_EXIT) {
                DestroyWindow(hWnd);
            } else if (cmd == ID_TRAY_AUTOSTART) {
                ToggleAutoStart(hWnd, !autoStartOn);
            } else if (cmd == ID_TRAY_ALTDRAG) {
                // No MessageBoxW here, unlike the autostart toggle: there's
                // no "did it actually work" uncertainty to report back (the
                // registry write is effectively instantaneous and reliable)
                // - the checkmark itself, visible the next time the menu
                // opens, is sufficient feedback for a simple preference.
                g_altDragEnabled = !g_altDragEnabled;
                SaveBoolSetting(ALTDRAG_REGVALUE, g_altDragEnabled);
            } else if (cmd == ID_TRAY_WINDRAG) {
                g_winDragEnabled = !g_winDragEnabled;
                SaveBoolSetting(WINDRAG_REGVALUE, g_winDragEnabled);
            }
        }
        return 0;

    case WM_DESTROY:
        Shell_NotifyIconW(NIM_DELETE, &g_nid);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

static HWND CreateTrayWindow(HINSTANCE hInstance) {
    WNDCLASSA wc = {0};
    wc.lpfnWndProc   = MainWndProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = "SmoothDrag_HiddenWnd";
    if (!RegisterClassA(&wc)) return NULL;

    return CreateWindowExA(0, "SmoothDrag_HiddenWnd", "SmoothDrag", 0,
                            0, 0, 0, 0, NULL, NULL, hInstance, NULL);
}

static void CopyHebrewTip(wchar_t *dst, size_t dstCapacity, const wchar_t *src) {
    size_t i = 0;
    for (; i < dstCapacity - 1 && src[i]; i++) dst[i] = src[i];
    dst[i] = L'\0';
}

static bool AddTrayIcon(HWND hWnd, HINSTANCE hInstance) {
    ZeroMemory(&g_nid, sizeof(g_nid));
    g_nid.cbSize           = sizeof(g_nid);
    g_nid.hWnd             = hWnd;
    g_nid.uID              = TRAY_ICON_UID;
    g_nid.uFlags           = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    g_nid.uCallbackMessage = WM_TRAYICON;
    g_nid.hIcon            = LoadIconW(hInstance, MAKEINTRESOURCEW(APP_ICON_RES_ID));
    CopyHebrewTip(g_nid.szTip, sizeof(g_nid.szTip) / sizeof(g_nid.szTip[0]),
                  L"SmoothDrag - גרירת חלונות");
    return Shell_NotifyIconW(NIM_ADD, &g_nid) != FALSE;
}

// ---------------------------------------------------------------------

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    (void)hPrevInstance; (void)lpCmdLine; (void)nCmdShow;

    // Single-instance guard. Name left as-is from the original version
    // of this program (predates the "SmoothDrag" naming) - intentionally
    // not renamed, so an already-installed/scheduled older build and a
    // freshly built one still recognize each other as the same instance
    // instead of both running at once.
    HANDLE hMutex = CreateMutexA(NULL, FALSE, "Local\\WinManager_Ultimate_25K_Safe");
    if (hMutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
        if (hMutex) CloseHandle(hMutex);
        return 0;
    }

    // One-time migration cleanup: this program used to be named
    // "SmoothDrag2" (the "2" had no real meaning and was dropped). An
    // install from before that rename may have registered its autostart
    // task under the old name "SmoothDrag2_AutoStart" - which the rest
    // of this file no longer knows about at all, since
    // IsAutoStartTaskRegistered()/CreateAutoStartTask()/
    // DeleteAutoStartTask() all only ever look at AUTOSTART_TASK_NAME
    // ("SmoothDrag_AutoStart", no "2"). Left alone, that old task would
    // sit there orphaned forever (harmlessly trying to launch a renamed
    // or deleted EXE at every logon), AND the new tray menu's checkbox
    // would show "off" even though autostart is technically still
    // enabled under the old registration - confusing either way. This
    // silently removes the old name if present; it's a harmless no-op
    // if it was never there. Cheap enough (one hidden schtasks.exe call)
    // to just always attempt at every startup rather than tracking
    // whether it's already been done once.
    {
        wchar_t legacySchtasksPath[MAX_PATH];
        if (GetSystemToolPath(legacySchtasksPath, MAX_PATH, L"schtasks.exe")) {
            wchar_t legacyCmd[600];
            wcscpy(legacyCmd, L"\"");
            wcscat(legacyCmd, legacySchtasksPath);
            wcscat(legacyCmd, L"\" /Delete /TN \"SmoothDrag2_AutoStart\" /F");
            RunHidden(legacyCmd, 3000); // short timeout, silent cleanup - must not delay startup
        }
    }


    HWND hWnd = CreateTrayWindow(hInstance);
    if (!hWnd) {
        CloseHandle(hMutex);
        return 1;
    }

    g_hInstance = hInstance;

    // This process runs elevated (requireAdministrator in the manifest)
    // while explorer.exe/the taskbar run at the normal user's integrity
    // level. UIPI blocks window messages sent from a LOWER integrity
    // process to a window owned by a HIGHER integrity one unless the
    // message is explicitly allowed - and our custom WM_TRAYICON
    // callback message is sent BY explorer.exe (non-elevated) TO our
    // window (elevated) every time the tray icon is clicked. Without
    // this call, that send is silently dropped: the icon shows up fine
    // (the other direction, us -> explorer when adding it, is
    // unaffected), but right-clicking it does nothing at all, with no
    // error anywhere to explain why.
    ChangeWindowMessageFilterEx(hWnd, WM_TRAYICON, MSGFLT_ALLOW, NULL);

    // Needed so MainWndProc can recognize and react to the
    // "TaskbarCreated" broadcast described in the comment above it.
    g_wmTaskbarCreated = RegisterWindowMessageW(L"TaskbarCreated");
    if (g_wmTaskbarCreated != 0) {
        ChangeWindowMessageFilterEx(hWnd, g_wmTaskbarCreated, MSGFLT_ALLOW, NULL);
    }

    // Do not treat a failed AddTrayIcon as fatal. At logon time, Task
    // Scheduler may fire the trigger before explorer.exe has finished
    // loading, which causes Shell_NotifyIconW(NIM_ADD) to fail even
    // though nothing is wrong with the program itself. If we exit here,
    // the process is gone and nobody is left to receive the
    // "TaskbarCreated" broadcast that explorer sends once it is ready -
    // so the icon would never appear at all. By continuing, we let the
    // message loop reach MainWndProc, which already calls AddTrayIcon()
    // again on every TaskbarCreated message. In practice the PT30S
    // <Delay> in the task XML makes the shell always ready before we
    // even start, so this branch is just a belt-and-suspenders fallback
    // for the unlikely case where 30 seconds was not enough (e.g. a
    // very slow machine). The hooks are fully operational regardless of
    // whether the tray icon was added successfully.
    AddTrayIcon(hWnd, hInstance);

    g_altDragEnabled = LoadBoolSetting(ALTDRAG_REGVALUE, true);
    g_winDragEnabled = LoadBoolSetting(WINDRAG_REGVALUE, true);

    g_dbclWidth  = GetSystemMetrics(SM_CXDOUBLECLK);
    g_dbclHeight = GetSystemMetrics(SM_CYDOUBLECLK);

    // Initialize the high-resolution timer frequency used by the resize
    // throttle in UpdateResize(). QueryPerformanceFrequency() always
    // succeeds on Windows XP and later; failure leaves g_qpcFreq.QuadPart
    // at 0, which UpdateResize() treats as "throttle disabled" (safe fallback).
    QueryPerformanceFrequency(&g_qpcFreq);

    // ABOVE_NORMAL, not NORMAL: the thread is asleep in GetMessage()
    // whenever nothing is happening, true - but "asleep" doesn't mean
    // "priority doesn't matter". Priority governs how fast a *sleeping*
    // thread gets rescheduled once its wait condition (an input event)
    // is satisfied, which is exactly what's at stake on every single
    // WM_MOUSEMOVE while a drag is in progress under system load (e.g.
    // something else hogging the CPU while you're dragging a window) -
    // that's the literal "Smooth" in SmoothDrag. ABOVE_NORMAL (not HIGH)
    // was chosen specifically because it can't starve the rest of the
    // system the way HIGH_PRIORITY_CLASS could if something here ever
    // misbehaves. (See WriteTaskXmlFile()'s comment above for why this
    // same priority is also set at the Task Scheduler level for the
    // auto-start case, where it matters even more.)
    SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);

    g_hHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, NULL, 0);
    if (!g_hHook) {
        DestroyWindow(hWnd);
        CloseHandle(hMutex);
        return 1;
    }

    // Always installed regardless of g_winDragEnabled/g_altDragEnabled's
    // current values - see LowLevelKeyboardProc's own comment for why
    // that's fine (it's a complete no-op unless g_winKeyUsedForDrag or
    // g_altKeyUsedForDrag was actually set, which in turn only happens
    // when a Win-drag or Alt-drag was actually started).
    g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0);
    if (!g_hKeyboardHook) {
        UnhookWindowsHookEx(g_hHook);
        DestroyWindow(hWnd);
        CloseHandle(hMutex);
        return 1;
    }

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    UnhookWindowsHookEx(g_hKeyboardHook);
    UnhookWindowsHookEx(g_hHook);
    CloseHandle(hMutex);
    return 0;
}