Skip to content

Instantly share code, notes, and snippets.

@m417z
Created June 27, 2026 21:25
Show Gist options
  • Select an option

  • Save m417z/1d5f68e81d7e3096d6c36f2a91af4db7 to your computer and use it in GitHub Desktop.

Select an option

Save m417z/1d5f68e81d7e3096d6c36f2a91af4db7 to your computer and use it in GitHub Desktop.
An archived version of a removed PR at https://github.com/ramensoftware/windhawk-mods/pull/4319
// ==WindhawkMod==
// @id custom-animations
// @name Animation & Wobbly Mod (Pure D2D)
// @description Ultra-smooth Open/Close/Minimize animation combined with Wobbly Windows physics. Fully powered by Direct2D.
// @version 1.3.1
// @author Shoaib Hassan
// @github https://github.com/shoaibhassan2
// @include *
// @compilerOptions -ldwmapi -lgdi32 -ld2d1 -luser32 -lcomctl32
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# Genie, Magic Lamp, Squash & Wobbly Animation Mod (Pure D2D Edition)
Replaces the default Windows minimize and restore animations with smooth geometry deformation effects.
Includes real-time wobbly window physics during dragging and resizing.
Both engines are now strictly powered by Direct2D for massive performance gains, low latency, and smooth anti-aliasing.
- KDE Magic Lamp: fluid 4-direction spatial bend
- macOS Genie: cosine-eased suction effect to dock
- MacSine: liquid wobble motion during animation
- Squash: scale and fade to/from taskbar icon
- Wobbly Windows: Interactive jelly physics when moving/resizing windows (D2D Ported).
*/
// ==/WindhawkModReadme==
// ==WindhawkModSettings==
/*
- duration_ms: 450
$name: Animation Duration (ms)
$description: Time it takes for the main animation to complete.
- animation_style: KDE
$name: Animation Style
$description: Choose the visual style of the minimize effect.
$options:
- KDE: KDE Magic Lamp
- MacPinch: macOS Genie (Pinch Effect)
- MacSine: macOS Genie (Sine Wave Wobble)
- Squash: KDE Squash (Scale + Fade to icon)
- bounce_enabled: true
$name: Enable Bounce Effect on Restore
$description: Adds an elastic pop effect when the window finishes restoring.
- bounce_strength: 30
$name: Bounce Strength (1-100)
$description: How intense the elasticity of the bounce is.
- bounce_duration: 300
$name: Bounce Duration (ms)
$description: How long the bounce effect lasts after the main animation finishes.
- wobbly_enabled: false
$name: Enable Wobbly Windows
$description: Wobble the window when moving or resizing it.
- wobbly_preset: 4
$name: Wobbly Physics Preset
$description: Adjust the stiffness and elasticity of the window dragging physics.
$options:
- 0: Very jelly / elastic
- 1: Soft wobble
- 2: Balanced smooth
- 3: KDE-like default
- 4: Very soft / floaty
*/
// ==/WindhawkModSettings==
#include <windows.h>
#include <dwmapi.h>
#include <d2d1.h>
#include <commctrl.h>
#include <tlhelp32.h>
#include <math.h>
#include <atomic>
#include <unordered_map>
#include <mutex>
#include <vector>
#ifndef DWMWA_EXTENDED_FRAME_BOUNDS
#define DWMWA_EXTENDED_FRAME_BOUNDS 9
#endif
#ifndef PW_RENDERFULLCONTENT
#define PW_RENDERFULLCONTENT 2
#endif
#define PI 3.14159265f
#define X_TILES 20
#define Y_TILES 20
// -------------------------------------------------------------------------
// Shared Structs & Typedefs
// -------------------------------------------------------------------------
struct Geometry { float x, y, width, height; };
enum IconPosition { POS_TOP, POS_BOTTOM, POS_LEFT, POS_RIGHT };
typedef LRESULT (WINAPI *DefWindowProcW_t)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
DefWindowProcW_t DefWindowProcW_Original;
typedef BOOL (WINAPI *ShowWindow_t)(HWND hWnd, int nCmdShow);
ShowWindow_t ShowWindow_Original;
using CreateWindowExW_t = HWND(WINAPI*)(DWORD, LPCWSTR, LPCWSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID);
CreateWindowExW_t CreateWindowExW_Original;
using CreateWindowExA_t = HWND(WINAPI*)(DWORD, LPCSTR, LPCSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID);
CreateWindowExA_t CreateWindowExA_Original;
DWORD WINAPI GhostAnimationThread(LPVOID lpParam);
// -------------------------------------------------------------------------
// Globals
// -------------------------------------------------------------------------
ID2D1Factory* g_d2dFactory = nullptr;
struct GhostAnimData {
HWND hGhost;
HWND hRealWnd;
HBITMAP hBitmap;
void* pBits;
RECT targetRect;
int width;
int height;
int targetDockX;
BOOL isRising;
LONG_PTR originalExStyle;
int durationMs;
bool bounceEnabled;
int bounceStrength;
int bounceDurationMs;
};
struct SnapCache { HBITMAP hBmp; void* pBits; int w; int h; };
std::unordered_map<HWND, SnapCache> g_SnapshotCache;
std::unordered_map<HWND, RECT> g_RectCache;
std::unordered_map<HWND, int> g_IconPositions;
std::mutex g_CacheMutex;
std::recursive_mutex g_wobblyMutex;
std::vector<HANDLE> g_animationThreads;
std::mutex g_threadsMutex;
std::vector<HWND> g_activeGhosts;
std::mutex g_ghostMutex;
std::atomic<bool> g_isUnloading{false};
HINSTANCE g_hInstance = NULL;
std::atomic<int> g_durationMs{450};
std::atomic<int> g_animationStyle{0};
std::atomic<bool> g_bounceEnabled{true};
std::atomic<int> g_bounceStrength{30};
std::atomic<int> g_bounceDurationMs{300};
// -------------------------------------------------------------------------
// Wobbly Windows State
// -------------------------------------------------------------------------
std::atomic<bool> g_engineInitialized{false};
UINT g_wmAttach = 0;
UINT g_wmDetach = 0;
struct Pair { float x, y; };
struct WobblyInfos {
Pair origin[16], position[16], velocity[16], acceleration[16], buffer[16];
bool constraint[16], wobblying, can_wobble_top, can_wobble_bottom, can_wobble_left, can_wobble_right;
};
struct ParameterSet { float stiffness, drag, moveFactor, minVelocity, maxVelocity, stopVelocity, minAcc, maxAcc, stopAcc; };
static const ParameterSet set_0 = { 0.15f, 0.80f, 0.10f, 0.0f, 1000.0f, 0.5f, 0.0f, 1000.0f, 0.5f };
static const ParameterSet set_1 = { 0.10f, 0.85f, 0.10f, 0.0f, 1000.0f, 0.5f, 0.0f, 1000.0f, 0.5f };
static const ParameterSet set_2 = { 0.06f, 0.90f, 0.10f, 0.0f, 1000.0f, 0.5f, 0.0f, 1000.0f, 0.5f };
static const ParameterSet set_3 = { 0.03f, 0.92f, 0.20f, 0.0f, 1000.0f, 0.5f, 0.0f, 1000.0f, 0.5f };
static const ParameterSet set_4 = { 0.01f, 0.97f, 0.25f, 0.0f, 1000.0f, 0.5f, 0.0f, 1000.0f, 0.5f };
static ParameterSet g_params = set_4;
std::atomic<bool> g_wobblyEnabled{false};
static HWND g_mainHwnd = NULL, g_overlayHwnd = NULL;
static int g_capX = 0, g_capY = 0, g_capW = 0, g_capH = 0;
static WobblyInfos g_wwi = {};
static bool g_isMoving = false, g_isSettling = false;
static DWORD g_lastTick = 0;
static LONG_PTR g_oldExStyle = 0;
static int g_screenX, g_screenY, g_screenW, g_screenH;
static Geometry g_currentRect = { 0 };
ID2D1DCRenderTarget* g_wobblyRT = nullptr;
ID2D1BitmapBrush* g_wobblyBrush = nullptr;
HBITMAP g_wobblyTargetBmp = NULL;
void* g_wobblyTargetBits = nullptr;
HDC g_wobblyMemDC = NULL;
// -------------------------------------------------------------------------
// Utils
// -------------------------------------------------------------------------
void LoadSettings() {
int ms = Wh_GetIntSetting(L"duration_ms");
if (ms < 50) ms = 50;
if (ms > 2000) ms = 2000;
g_durationMs.store(ms, std::memory_order_relaxed);
PCWSTR styleStr = Wh_GetStringSetting(L"animation_style");
int style = 0;
if (styleStr) {
if (wcscmp(styleStr, L"MacPinch") == 0) style = 1;
else if (wcscmp(styleStr, L"MacSine") == 0) style = 2;
else if (wcscmp(styleStr, L"Squash") == 0) style = 3;
Wh_FreeStringSetting(styleStr);
}
g_animationStyle.store(style, std::memory_order_relaxed);
g_bounceEnabled.store(Wh_GetIntSetting(L"bounce_enabled") != 0, std::memory_order_relaxed);
g_bounceStrength.store(Wh_GetIntSetting(L"bounce_strength"), std::memory_order_relaxed);
g_bounceDurationMs.store(Wh_GetIntSetting(L"bounce_duration"), std::memory_order_relaxed);
g_wobblyEnabled.store(Wh_GetIntSetting(L"wobbly_enabled") != 0, std::memory_order_relaxed);
int preset = Wh_GetIntSetting(L"wobbly_preset");
switch (preset) {
case 0: g_params = set_0; break;
case 1: g_params = set_1; break;
case 2: g_params = set_2; break;
case 3: g_params = set_3; break;
case 4: default: g_params = set_4; break;
}
}
void SetDwmTransitions(HWND hWnd, BOOL enable) {
BOOL disable = !enable;
DwmSetWindowAttribute(hWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &disable, sizeof(disable));
}
static ID2D1PathGeometry* CreateQuadGeo(
ID2D1Factory* factory,
D2D1_POINT_2F p0, D2D1_POINT_2F p1,
D2D1_POINT_2F p2, D2D1_POINT_2F p3)
{
ID2D1PathGeometry* geo = nullptr;
factory->CreatePathGeometry(&geo);
if (!geo) return nullptr;
ID2D1GeometrySink* sink = nullptr;
geo->Open(&sink);
sink->BeginFigure(p0, D2D1_FIGURE_BEGIN_FILLED);
sink->AddLine(p1);
sink->AddLine(p2);
sink->AddLine(p3);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
sink->Close();
sink->Release();
return geo;
}
static D2D1_POINT_2F BloatPoint(D2D1_POINT_2F p, D2D1_POINT_2F c) {
float dx = p.x - c.x; float dy = p.y - c.y;
float len = sqrt(dx*dx + dy*dy);
if (len < 0.001f) return p;
return D2D1::Point2F(p.x + (dx/len)*0.5f, p.y + (dy/len)*0.5f);
}
// -------------------------------------------------------------------------
// Custom Animation Engine
// -------------------------------------------------------------------------
static inline float CubicEaseIn(float t) { return t * t * t; }
static inline float CubicEaseOut(float t) { float t1 = t - 1.0f; return t1 * t1 * t1 + 1.0f; }
static void CalculateLampVertexKDE(float tx, float ty, float p, const Geometry& w, const Geometry& i, int pos, float *outX, float *outY) {
float quadX = tx * w.width;
float quadY = ty * w.height;
float quadFactor, offset, denom, p_progress, absX, targetX, targetY;
if (pos == POS_BOTTOM) {
float h3 = w.height * w.height * w.height;
float maxY = i.y - w.y;
quadFactor = quadY + (w.height - quadY) * p;
offset = (i.y + quadY - w.y) * p * ((quadFactor * quadFactor * quadFactor) / h3);
denom = i.y - w.y - quadY;
p_progress = (denom != 0.0f) ? fminf(offset / denom, 1.0f) : 1.0f;
p_progress = fabsf(p_progress);
targetX = i.x + i.width * tx;
absX = quadX + w.x;
*outX = (targetX - absX) * p_progress + absX;
*outY = w.y + fminf(maxY, quadY + offset);
} else if (pos == POS_TOP) {
float h3 = w.height * w.height * w.height;
float minY = i.y + i.height - w.y;
quadFactor = w.height - quadY + quadY * p;
offset = (w.y - i.height + w.height + quadY - i.y) * p * ((quadFactor * quadFactor * quadFactor) / h3);
denom = w.y - i.height - i.y + quadY;
p_progress = (denom != 0.0f) ? fminf(offset / denom, 1.0f) : 1.0f;
offset = -offset;
p_progress = fabsf(p_progress);
targetX = i.x + i.width * tx;
absX = quadX + w.x;
*outX = (targetX - absX) * p_progress + absX;
*outY = w.y + fmaxf(minY, quadY + offset);
} else if (pos == POS_LEFT) {
float w3 = w.width * w.width * w.width;
float minX = i.x + i.width - w.x;
quadFactor = w.width - quadX + quadX * p;
offset = (w.x - i.width + w.width + quadX - i.x) * p * ((quadFactor * quadFactor * quadFactor) / w3);
denom = w.x - i.width - i.x + quadX;
p_progress = (denom != 0.0f) ? fminf(offset / denom, 1.0f) : 1.0f;
offset = -offset;
p_progress = fabsf(p_progress);
targetY = i.y + i.height * ty;
float absY = quadY + w.y;
*outY = (targetY - absY) * p_progress + absY;
*outX = w.x + fmaxf(minX, quadX + offset);
} else if (pos == POS_RIGHT) {
float w3 = w.width * w.width * w.width;
float maxX = i.x - w.x;
quadFactor = quadX + (w.width - quadX) * p;
offset = (i.x + quadX - w.x) * p * ((quadFactor * quadFactor * quadFactor) / w3);
denom = i.x - w.x - quadX;
p_progress = (denom != 0.0f) ? fminf(offset / denom, 1.0f) : 1.0f;
p_progress = fabsf(p_progress);
targetY = i.y + i.height * ty;
float absY = quadY + w.y;
*outY = (targetY - absY) * p_progress + absY;
*outX = w.x + fminf(maxX, quadX + offset);
}
}
static void CalculateLampVertexMacOS(float tx, float ty, float p, const Geometry& w, const Geometry& i, int style, float *outX, float *outY) {
float split = 0.3f;
float k = (p <= split) ? (p / split) : 1.0f;
float j = (p > split) ? ((p - split) / (1.0f - split)) : 0.0f;
float expandHeight = (i.y - w.y - w.height);
float fullHeight = (i.y - w.y) - (expandHeight * (1.0f - k));
float height = fullHeight - (j * fullHeight);
float y = ty * height;
float x = tx * (i.width) + tx * (w.width - i.width) * (1.0f - j) * (1.0f - ty) + tx * (w.width - i.width) * (1.0f - k) * ty;
float offsetX = (i.x - w.x) * (y / (fullHeight + 0.1f)) * k + (i.x - w.x) * j;
float offsetY = i.y - w.y - height - (expandHeight * (1.0f - k));
float effectX;
if (style == 2) {
effectX = sinf((height - y) / fullHeight * PI * 4.0f) * w.width / 14.0f * k;
} else {
effectX = sinf(((height - y) / fullHeight) * 2.0f * PI + PI) * (w.x + w.width * tx - (i.x + i.width * tx)) / 7.0f * k;
}
*outX = w.x + x + offsetX + effectX;
*outY = w.y + y + offsetY;
}
void ShowGhostSync(GhostAnimData* data) {
if (!data || !data->hGhost) return;
int vLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
int vTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
int vWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int vHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
HDC hScreenDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hScreenDC);
BITMAPINFO bmi = {{0}};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = vWidth;
bmi.bmiHeader.biHeight = -vHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* pTargetBits = nullptr;
HBITMAP hTargetBmp = CreateDIBSection(hScreenDC, &bmi, DIB_RGB_COLORS, &pTargetBits, NULL, 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hTargetBmp);
HDC hSnapDC = CreateCompatibleDC(hScreenDC);
HBITMAP hOldSnap = (HBITMAP)SelectObject(hSnapDC, data->hBitmap);
BitBlt(hMemDC, data->targetRect.left - vLeft, data->targetRect.top - vTop, data->width, data->height, hSnapDC, 0, 0, SRCCOPY);
SelectObject(hSnapDC, hOldSnap);
DeleteDC(hSnapDC);
POINT ptDst = { vLeft, vTop };
SIZE sz = { vWidth, vHeight };
POINT ptSrc = { 0, 0 };
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
UpdateLayeredWindow(data->hGhost, NULL, &ptDst, &sz, hMemDC, &ptSrc, 0, &bf, ULW_ALPHA);
ShowWindow(data->hGhost, SW_SHOWNOACTIVATE);
SelectObject(hMemDC, hOldBmp);
DeleteObject(hTargetBmp);
DeleteDC(hMemDC);
ReleaseDC(NULL, hScreenDC);
}
void SpawnAnimationThread(GhostAnimData* data) {
std::lock_guard<std::mutex> lock(g_threadsMutex);
if (g_isUnloading.load(std::memory_order_relaxed)) {
delete data;
return;
}
for (auto it = g_animationThreads.begin(); it != g_animationThreads.end(); ) {
if (WaitForSingleObject(*it, 0) == WAIT_OBJECT_0) {
CloseHandle(*it);
it = g_animationThreads.erase(it);
} else {
++it;
}
}
HANDLE hThread = CreateThread(NULL, 0, GhostAnimationThread, data, 0, NULL);
if (hThread) {
g_animationThreads.push_back(hThread);
} else {
delete data; // Fallback to prevent memory leak if creation fails
}
}
DWORD WINAPI GhostAnimationThread(LPVOID lpParam) {
GhostAnimData* data = (GhostAnimData*)lpParam;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
int style = g_animationStyle.load(std::memory_order_relaxed);
int xTiles = (style == 1 || style == 2) ? 60 : 40;
int yTiles = (style == 1 || style == 2) ? 60 : 40;
int vLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
int vTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
int vWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int vHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
HWND hGhost = data->hGhost;
HDC hScreenDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hScreenDC);
BITMAPINFO bmi = {{0}};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = vWidth;
bmi.bmiHeader.biHeight = -vHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* pTargetBits = nullptr;
HBITMAP hTargetBmp = CreateDIBSection(hScreenDC, &bmi, DIB_RGB_COLORS, &pTargetBits, NULL, 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hTargetBmp);
ID2D1DCRenderTarget* rt = nullptr;
ID2D1Bitmap* snapshotBmp = nullptr;
ID2D1BitmapBrush* bmpBrush = nullptr;
if (g_d2dFactory) {
D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_SOFTWARE,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
0, 0, D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, D2D1_FEATURE_LEVEL_DEFAULT
);
g_d2dFactory->CreateDCRenderTarget(&rtProps, &rt);
if (rt) {
D2D1_BITMAP_PROPERTIES bmpProps = D2D1::BitmapProperties(
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
);
rt->CreateBitmap(D2D1::SizeU(data->width, data->height), data->pBits, data->width * 4, bmpProps, &snapshotBmp);
if (snapshotBmp) {
D2D1_BITMAP_BRUSH_PROPERTIES brushProps = D2D1::BitmapBrushProperties(
D2D1_EXTEND_MODE_CLAMP, D2D1_EXTEND_MODE_CLAMP,
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
);
rt->CreateBitmapBrush(snapshotBmp, &brushProps, nullptr, &bmpBrush);
}
}
}
Geometry wGeom = { (float)data->targetRect.left, (float)data->targetRect.top, (float)data->width, (float)data->height };
Geometry iGeom = { (float)data->targetDockX - 20.0f, (float)screenHeight - 40.0f, 40.0f, 40.0f };
int position = POS_BOTTOM;
float dx = (iGeom.x + iGeom.width / 2.0f) - (wGeom.x + wGeom.width / 2.0f);
float dy = (iGeom.y + iGeom.height / 2.0f) - (wGeom.y + wGeom.height / 2.0f);
if (fabsf(dx) > fabsf(dy)) position = (dx > 0) ? POS_RIGHT : POS_LEFT;
else position = (dy > 0) ? POS_BOTTOM : POS_TOP;
float targetScaleX = iGeom.width / wGeom.width;
float targetScaleY = iGeom.height / wGeom.height;
float startX = wGeom.x - vLeft;
float startY = wGeom.y - vTop;
float endX = iGeom.x - vLeft;
float endY = iGeom.y - vTop;
const double animDur = (double)data->durationMs;
const double bounceDur = (data->isRising && data->bounceEnabled) ? (double)data->bounceDurationMs : 0.0;
const double totalMs = animDur + bounceDur;
LARGE_INTEGER qpcFreq, qpcStart, qpcNow;
QueryPerformanceFrequency(&qpcFreq);
QueryPerformanceCounter(&qpcStart);
std::vector<std::vector<D2D1_POINT_2F>> grid(yTiles + 1, std::vector<D2D1_POINT_2F>(xTiles + 1));
BOOL firstFrame = TRUE;
if (!rt || !bmpBrush) {
if (data->isRising) {
SetLayeredWindowAttributes(data->hRealWnd, 0, 255, LWA_ALPHA);
if (!(data->originalExStyle & WS_EX_LAYERED)) {
SetWindowLongPtrW(data->hRealWnd, GWL_EXSTYLE, data->originalExStyle);
}
}
SetDwmTransitions(data->hRealWnd, TRUE);
if (bmpBrush) { bmpBrush->Release(); bmpBrush = nullptr; }
if (snapshotBmp) { snapshotBmp->Release(); snapshotBmp = nullptr; }
if (rt) { rt->Release(); rt = nullptr; }
SelectObject(hMemDC, hOldBmp); DeleteObject(hTargetBmp); DeleteObject(data->hBitmap);
DeleteDC(hMemDC); ReleaseDC(NULL, hScreenDC);
PostMessage(hGhost, WM_CLOSE, 0, 0);
delete data;
return 0;
}
for (;;) {
if (g_isUnloading.load(std::memory_order_relaxed)) break;
QueryPerformanceCounter(&qpcNow);
double elapsedMs = (qpcNow.QuadPart - qpcStart.QuadPart) * 1000.0 / qpcFreq.QuadPart;
BOOL lastFrame = (elapsedMs >= totalMs);
float raw_p = (float)fmin(elapsedMs / animDur, 1.0);
float t;
if (style == 1 || style == 2) {
float eased_p = 0.5f * (1.0f - cosf(raw_p * PI));
t = data->isRising ? (1.0f - eased_p) : eased_p;
} else if (style == 3) {
t = data->isRising ? CubicEaseOut(raw_p) : CubicEaseIn(raw_p);
} else {
t = data->isRising ? (1.0f - raw_p) : raw_p;
}
float bounceScale = 1.0f;
if (data->isRising && data->bounceEnabled && elapsedMs > animDur) {
float tB = (float)((elapsedMs - animDur) / bounceDur);
if (tB > 1.0f) tB = 1.0f;
float amp = data->bounceStrength / 100.0f * 0.16f;
bounceScale = 1.0f + amp * sinf(tB * PI);
}
if (rt && bmpBrush) {
RECT bindRect = { 0, 0, vWidth, vHeight };
rt->BindDC(hMemDC, &bindRect);
rt->BeginDraw();
rt->Clear(D2D1::ColorF(0, 0, 0, 0));
rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
float opacity = 1.0f;
if (style == 3) {
float transX, transY, scaleX, scaleY;
if (data->isRising) {
transX = endX + (startX - endX) * t;
transY = endY + (startY - endY) * t;
scaleX = targetScaleX + (1.0f - targetScaleX) * t;
scaleY = targetScaleY + (1.0f - targetScaleY) * t;
opacity = t;
} else {
transX = startX + (endX - startX) * t;
transY = startY + (endY - startY) * t;
scaleX = 1.0f + (targetScaleX - 1.0f) * t;
scaleY = 1.0f + (targetScaleY - 1.0f) * t;
opacity = 1.0f - t;
}
if (bounceScale != 1.0f) {
float currentW = scaleX * data->width;
float currentH = scaleY * data->height;
transX -= (currentW * (bounceScale - 1.0f)) / 2.0f;
transY -= (currentH * (bounceScale - 1.0f)) / 2.0f;
scaleX *= bounceScale;
scaleY *= bounceScale;
}
D2D1_MATRIX_3X2_F transform = D2D1::Matrix3x2F::Scale(scaleX, scaleY) * D2D1::Matrix3x2F::Translation(transX, transY);
rt->SetTransform(transform);
D2D1_RECT_F rect = D2D1::RectF(0, 0, wGeom.width, wGeom.height);
rt->DrawBitmap(snapshotBmp, &rect);
rt->SetTransform(D2D1::Matrix3x2F::Identity());
} else {
Geometry currentWGeom = wGeom;
if (bounceScale != 1.0f) {
float centerX = wGeom.x + wGeom.width / 2.0f;
float centerY = wGeom.y + wGeom.height / 2.0f;
currentWGeom.width = wGeom.width * bounceScale;
currentWGeom.height = wGeom.height * bounceScale;
currentWGeom.x = centerX - currentWGeom.width / 2.0f;
currentWGeom.y = centerY - currentWGeom.height / 2.0f;
}
for (int y = 0; y <= yTiles; y++) {
for (int x = 0; x <= xTiles; x++) {
float tx = (float)x / xTiles, ty = (float)y / yTiles;
float px, py;
if (style == 1 || style == 2) {
CalculateLampVertexMacOS(tx, ty, t, currentWGeom, iGeom, style, &px, &py);
} else {
CalculateLampVertexKDE(tx, ty, t, currentWGeom, iGeom, position, &px, &py);
}
grid[y][x] = D2D1::Point2F(px - vLeft, py - vTop);
}
}
ID2D1PathGeometry* outlineGeo = nullptr;
g_d2dFactory->CreatePathGeometry(&outlineGeo);
if (outlineGeo) {
ID2D1GeometrySink* sink = nullptr;
outlineGeo->Open(&sink);
sink->BeginFigure(grid[0][0], D2D1_FIGURE_BEGIN_FILLED);
for (int x = 1; x <= xTiles; x++) sink->AddLine(grid[0][x]);
for (int y = 1; y <= yTiles; y++) sink->AddLine(grid[y][xTiles]);
for (int x = xTiles - 1; x >= 0; x--) sink->AddLine(grid[yTiles][x]);
for (int y = yTiles - 1; y >= 0; y--) sink->AddLine(grid[y][0]);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
sink->Close();
ID2D1Layer* layer = nullptr;
rt->CreateLayer(&layer);
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
layerParams.geometricMask = outlineGeo;
layerParams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
rt->PushLayer(&layerParams, layer);
for (int y = 0; y < yTiles; y++) {
for (int x = 0; x < xTiles; x++) {
D2D1_POINT_2F p1 = grid[y][x];
D2D1_POINT_2F p2 = grid[y][x+1];
D2D1_POINT_2F p3 = grid[y+1][x];
D2D1_POINT_2F p4 = grid[y+1][x+1];
D2D1_POINT_2F c = D2D1::Point2F((p1.x+p2.x+p3.x+p4.x)/4.0f, (p1.y+p2.y+p3.y+p4.y)/4.0f);
ID2D1PathGeometry* quadGeo = CreateQuadGeo(g_d2dFactory,
BloatPoint(p1, c), BloatPoint(p2, c), BloatPoint(p4, c), BloatPoint(p3, c));
if (quadGeo) {
float sx = ((float)x / xTiles) * wGeom.width;
float sy = ((float)y / yTiles) * wGeom.height;
float sw = wGeom.width / xTiles;
float sh = wGeom.height / yTiles;
float m11 = (p2.x - p1.x) / sw;
float m12 = (p2.y - p1.y) / sw;
float m21 = (p3.x - p1.x) / sh;
float m22 = (p3.y - p1.y) / sh;
float m31 = p1.x - sx * m11 - sy * m21;
float m32 = p1.y - sx * m12 - sy * m22;
bmpBrush->SetTransform(D2D1::Matrix3x2F(m11, m12, m21, m22, m31, m32));
rt->FillGeometry(quadGeo, bmpBrush);
quadGeo->Release();
}
}
}
rt->PopLayer();
if (layer) layer->Release();
outlineGeo->Release();
}
}
rt->EndDraw();
POINT ptDst = { vLeft, vTop };
SIZE sz = { vWidth, vHeight };
POINT ptSrc = { 0, 0 };
BLENDFUNCTION bf = { AC_SRC_OVER, 0, (BYTE)(opacity * 255.0f), AC_SRC_ALPHA };
UpdateLayeredWindow(hGhost, NULL, &ptDst, &sz, hMemDC, &ptSrc, 0, &bf, ULW_ALPHA);
}
if (firstFrame) {
ShowWindow(hGhost, SW_SHOWNOACTIVATE);
firstFrame = FALSE;
}
if (lastFrame) break;
DwmFlush();
}
if (data->isRising) {
SetLayeredWindowAttributes(data->hRealWnd, 0, 255, LWA_ALPHA);
if (!(data->originalExStyle & WS_EX_LAYERED)) {
SetWindowLongPtrW(data->hRealWnd, GWL_EXSTYLE, data->originalExStyle);
}
}
SetDwmTransitions(data->hRealWnd, TRUE);
if (bmpBrush) { bmpBrush->Release(); bmpBrush = nullptr; }
if (snapshotBmp) { snapshotBmp->Release(); snapshotBmp = nullptr; }
if (rt) { rt->Release(); rt = nullptr; }
SelectObject(hMemDC, hOldBmp);
DeleteObject(hTargetBmp);
DeleteObject(data->hBitmap);
DeleteDC(hMemDC);
ReleaseDC(NULL, hScreenDC);
// Cross-thread teardown
ShowWindow(hGhost, SW_HIDE);
PostMessage(hGhost, WM_CLOSE, 0, 0);
delete data;
return 0;
}
static LRESULT CALLBACK GhostProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_CLOSE) {
DestroyWindow(hwnd);
return 0;
} else if (msg == WM_NCDESTROY) {
std::lock_guard<std::mutex> lock(g_ghostMutex);
for (auto it = g_activeGhosts.begin(); it != g_activeGhosts.end(); ) {
if (*it == hwnd) it = g_activeGhosts.erase(it);
else ++it;
}
}
return DefWindowProcA(hwnd, msg, wp, lp);
}
GhostAnimData* PrepareGenieAnim(HWND hWnd, BOOL rising) {
RECT winRect;
GetWindowRect(hWnd, &winRect);
RECT rect = winRect;
RECT extRect;
if (SUCCEEDED(DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &extRect, sizeof(extRect)))) {
rect = extRect;
}
if (rising) {
std::lock_guard<std::mutex> lock(g_CacheMutex);
if (g_RectCache.count(hWnd)) {
rect = g_RectCache[hWnd];
}
} else {
std::lock_guard<std::mutex> lock(g_CacheMutex);
g_RectCache[hWnd] = rect;
}
int w = rect.right - rect.left;
int h = rect.bottom - rect.top;
int offsetX = rect.left - winRect.left;
int offsetY = rect.top - winRect.top;
int rawW = winRect.right - winRect.left;
int rawH = winRect.bottom - winRect.top;
if (w <= 0 || h <= 0) return nullptr;
POINT pt;
GetCursorPos(&pt);
RECT workArea;
SystemParametersInfoW(SPI_GETWORKAREA, 0, &workArea, 0);
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int learnedTargetX = screenWidth / 2;
if (!PtInRect(&workArea, pt)) {
learnedTargetX = pt.x;
std::lock_guard<std::mutex> lock(g_CacheMutex);
g_IconPositions[hWnd] = learnedTargetX;
} else {
std::lock_guard<std::mutex> lock(g_CacheMutex);
if (g_IconPositions.count(hWnd)) {
learnedTargetX = g_IconPositions[hWnd];
}
}
GhostAnimData* data = new GhostAnimData();
data->hRealWnd = hWnd;
data->targetRect = rect;
data->width = w;
data->height = h;
data->isRising = rising;
data->targetDockX = learnedTargetX;
data->originalExStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
data->durationMs = g_durationMs.load(std::memory_order_relaxed);
data->bounceEnabled = g_bounceEnabled.load(std::memory_order_relaxed);
data->bounceStrength = g_bounceStrength.load(std::memory_order_relaxed);
data->bounceDurationMs = g_bounceDurationMs.load(std::memory_order_relaxed);
HDC hScreenDC = GetDC(NULL);
BITMAPINFO bmi = {{0}};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
data->hBitmap = CreateDIBSection(hScreenDC, &bmi, DIB_RGB_COLORS, &(data->pBits), NULL, 0);
auto CopyAndFixAlpha = [&](void* srcBits, void* dstBits) {
DWORD* src = (DWORD*)srcBits;
DWORD* dst = (DWORD*)dstBits;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int sx = x + offsetX;
int sy = y + offsetY;
if (sx >= 0 && sx < rawW && sy >= 0 && sy < rawH) {
DWORD p = src[sy * rawW + sx];
BYTE a = (p >> 24) & 0xFF;
if (a == 0) {
dst[y * w + x] = 0;
} else if (a == 255) {
dst[y * w + x] = p;
} else {
BYTE r = (p >> 16) & 0xFF;
BYTE g = (p >> 8) & 0xFF;
BYTE b = p & 0xFF;
dst[y * w + x] = (a << 24) | (((r * a) / 255) << 16) | (((g * a) / 255) << 8) | ((b * a) / 255);
}
} else {
dst[y * w + x] = 0;
}
}
}
};
if (rising) {
BOOL fromCache = FALSE;
{
std::lock_guard<std::mutex> lock(g_CacheMutex);
if (g_SnapshotCache.count(hWnd)) {
SnapCache& c = g_SnapshotCache[hWnd];
if (c.w == w && c.h == h) {
memcpy(data->pBits, c.pBits, w * h * 4);
fromCache = TRUE;
}
DeleteObject(c.hBmp);
g_SnapshotCache.erase(hWnd);
}
}
if (!fromCache) {
DeleteObject(data->hBitmap);
delete data;
ReleaseDC(NULL, hScreenDC);
return nullptr;
}
} else {
HDC hTempDC = CreateCompatibleDC(hScreenDC);
BITMAPINFO bmiTemp = bmi;
bmiTemp.bmiHeader.biWidth = rawW;
bmiTemp.bmiHeader.biHeight = -rawH;
void* pTempBits = nullptr;
HBITMAP hTempBmp = CreateDIBSection(hScreenDC, &bmiTemp, DIB_RGB_COLORS, &pTempBits, NULL, 0);
HBITMAP hOldTempBmp = (HBITMAP)SelectObject(hTempDC, hTempBmp);
PrintWindow(hWnd, hTempDC, PW_RENDERFULLCONTENT);
GdiFlush();
CopyAndFixAlpha(pTempBits, data->pBits);
SelectObject(hTempDC, hOldTempBmp);
DeleteObject(hTempBmp);
DeleteDC(hTempDC);
std::lock_guard<std::mutex> lock(g_CacheMutex);
if (g_SnapshotCache.count(hWnd)) {
DeleteObject(g_SnapshotCache[hWnd].hBmp);
}
void* pCacheBits = nullptr;
HBITMAP hCacheBmp = CreateDIBSection(hScreenDC, &bmi, DIB_RGB_COLORS, &pCacheBits, NULL, 0);
memcpy(pCacheBits, data->pBits, w * h * 4);
g_SnapshotCache[hWnd] = { hCacheBmp, pCacheBits, w, h };
}
ReleaseDC(NULL, hScreenDC);
int vLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
int vTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
int vWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int vHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
data->hGhost = CreateWindowExA(
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT,
"GhostWindowClass", NULL, WS_POPUP,
vLeft, vTop, vWidth, vHeight,
NULL, NULL, g_hInstance, NULL
);
if (!data->hGhost) {
DeleteObject(data->hBitmap);
delete data;
return nullptr;
}
std::lock_guard<std::mutex> lock(g_ghostMutex);
g_activeGhosts.push_back(data->hGhost);
return data;
}
// -------------------------------------------------------------------------
// Wobbly Windows Engine
// -------------------------------------------------------------------------
static void CaptureWindowForWobbly(HWND hwnd) {
RECT rcWin, rcExt;
GetWindowRect(hwnd, &rcWin);
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcExt, sizeof(rcExt)) != S_OK) {
rcExt = rcWin;
}
int rawW = rcWin.right - rcWin.left;
int rawH = rcWin.bottom - rcWin.top;
g_capX = rcExt.left - rcWin.left;
g_capY = rcExt.top - rcWin.top;
g_capW = rcExt.right - rcExt.left;
g_capH = rcExt.bottom - rcExt.top;
if (rawW <= 0 || rawH <= 0 || g_capW <= 0 || g_capH <= 0) return;
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
BITMAPINFO bmi = {{sizeof(BITMAPINFOHEADER), rawW, -rawH, 1, 32, BI_RGB}};
void* rawBits = nullptr;
HBITMAP hbmRaw = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &rawBits, NULL, 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hbmRaw);
PrintWindow(hwnd, hdcMem, PW_RENDERFULLCONTENT);
// Pre-multiply alpha for D2D (fixes Win11 rounded corners)
BYTE* pRaw = (BYTE*)rawBits;
for (int i = 0; i < rawW * rawH; i++) {
BYTE a = pRaw[i * 4 + 3];
if (a == 255) continue;
if (a == 0) {
pRaw[i * 4 + 0] = 0;
pRaw[i * 4 + 1] = 0;
pRaw[i * 4 + 2] = 0;
} else {
pRaw[i * 4 + 0] = (pRaw[i * 4 + 0] * a) / 255;
pRaw[i * 4 + 1] = (pRaw[i * 4 + 1] * a) / 255;
pRaw[i * 4 + 2] = (pRaw[i * 4 + 2] * a) / 255;
}
}
static int g_bmpW = 0, g_bmpH = 0;
// Fetch fresh screen metrics
g_screenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
g_screenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
g_screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
g_screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Reposition the overlay to match current screen size
if (g_overlayHwnd) {
SetWindowPos(g_overlayHwnd, NULL, g_screenX, g_screenY, g_screenW, g_screenH, SWP_NOZORDER | SWP_NOACTIVATE);
}
// Destroy old targets if the resolution changed
if (g_bmpW != g_screenW || g_bmpH != g_screenH) {
if (g_wobblyMemDC) { DeleteDC(g_wobblyMemDC); g_wobblyMemDC = NULL; }
if (g_wobblyTargetBmp) { DeleteObject(g_wobblyTargetBmp); g_wobblyTargetBmp = NULL; }
if (g_wobblyRT) { g_wobblyRT->Release(); g_wobblyRT = nullptr; }
g_bmpW = g_screenW;
g_bmpH = g_screenH;
}
if (!g_wobblyMemDC) {
g_wobblyMemDC = CreateCompatibleDC(hdcScreen);
BITMAPINFO bmiScreen = {{sizeof(BITMAPINFOHEADER), g_screenW, -g_screenH, 1, 32, BI_RGB}};
g_wobblyTargetBmp = CreateDIBSection(hdcScreen, &bmiScreen, DIB_RGB_COLORS, &g_wobblyTargetBits, NULL, 0);
SelectObject(g_wobblyMemDC, g_wobblyTargetBmp);
}
if (!g_wobblyRT && g_d2dFactory) {
D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_SOFTWARE,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
0, 0, D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, D2D1_FEATURE_LEVEL_DEFAULT
);
g_d2dFactory->CreateDCRenderTarget(&rtProps, &g_wobblyRT);
}
if (g_wobblyRT) {
if (g_wobblyBrush) { g_wobblyBrush->Release(); g_wobblyBrush = nullptr; }
ID2D1Bitmap* pRawBmp = nullptr;
D2D1_BITMAP_PROPERTIES bmpProps = D2D1::BitmapProperties(
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
);
g_wobblyRT->CreateBitmap(D2D1::SizeU(rawW, rawH), rawBits, rawW * 4, bmpProps, &pRawBmp);
if (pRawBmp) {
D2D1_BITMAP_BRUSH_PROPERTIES brushProps = D2D1::BitmapBrushProperties(
D2D1_EXTEND_MODE_CLAMP, D2D1_EXTEND_MODE_CLAMP, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
);
g_wobblyRT->CreateBitmapBrush(pRawBmp, &brushProps, nullptr, &g_wobblyBrush);
pRawBmp->Release();
}
}
SelectObject(hdcMem, hOldBmp);
DeleteObject(hbmRaw);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
static void UpdateWobblyOrigin(float x, float y, float w, float h) {
g_currentRect.x = x; g_currentRect.y = y; g_currentRect.width = w; g_currentRect.height = h;
float x_length = w / 3.0f, y_length = h / 3.0f;
Pair origine = { x, y };
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 4; ++i) {
g_wwi.origin[j * 4 + i] = origine;
if (i != 2) origine.x += x_length;
else origine.x = w + x;
}
origine.x = x;
if (j != 2) origine.y += y_length;
else origine.y = h + y;
}
}
static void InitWobblyInfo(float x, float y, float w, float h) {
memset(&g_wwi, 0, sizeof(WobblyInfos));
UpdateWobblyOrigin(x, y, w, h);
for (int i = 0; i < 16; ++i) g_wwi.position[i] = g_wwi.origin[i];
g_wwi.can_wobble_top = true; g_wwi.can_wobble_bottom = true;
g_wwi.can_wobble_left = true; g_wwi.can_wobble_right = true;
g_wwi.wobblying = true;
}
static void HeightRingLinearMean(Pair* data, Pair* buffer) {
for (int i = 0; i < 16; ++i) {
int x = i % 4, y = i / 4, weight = 0;
float sum_x = 0, sum_y = 0;
for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) {
int nx = x + dx, ny = y + dy;
if (nx >= 0 && nx <= 3 && ny >= 0 && ny <= 3) {
if (dx == 0 && dy == 0) continue;
sum_x += data[ny * 4 + nx].x; sum_y += data[ny * 4 + nx].y; weight++;
}
}
}
sum_x += data[i].x * weight; sum_y += data[i].y * weight;
buffer[i].x = sum_x / (weight * 2.0f); buffer[i].y = sum_y / (weight * 2.0f);
}
for (int i = 0; i < 16; ++i) data[i] = buffer[i];
}
static void StepPhysics(float time) {
float x_length = g_currentRect.width / 3.0f, y_length = g_currentRect.height / 3.0f;
float acc_sum = 0.0f, vel_sum = 0.0f;
for (int i = 0; i < 16; ++i) {
if (g_wwi.constraint[i]) {
g_wwi.acceleration[i].x = (g_wwi.origin[i].x - g_wwi.position[i].x) * g_params.stiffness;
g_wwi.acceleration[i].y = (g_wwi.origin[i].y - g_wwi.position[i].y) * g_params.stiffness;
} else {
float ax = 0, ay = 0; int count = 0; int x = i % 4, y = i / 4;
if (x < 3) { ax += (g_wwi.position[i + 1].x - g_wwi.position[i].x - x_length) * g_params.stiffness; ay += (g_wwi.position[i + 1].y - g_wwi.position[i].y) * g_params.stiffness; count++; }
if (x > 0) { ax += (g_wwi.position[i - 1].x - g_wwi.position[i].x + x_length) * g_params.stiffness; ay += (g_wwi.position[i - 1].y - g_wwi.position[i].y) * g_params.stiffness; count++; }
if (y < 3) { ax += (g_wwi.position[i + 4].x - g_wwi.position[i].x) * g_params.stiffness; ay += (g_wwi.position[i + 4].y - g_wwi.position[i].y - y_length) * g_params.stiffness; count++; }
if (y > 0) { ax += (g_wwi.position[i - 4].x - g_wwi.position[i].x) * g_params.stiffness; ay += (g_wwi.position[i - 4].y - g_wwi.position[i].y + y_length) * g_params.stiffness; count++; }
g_wwi.acceleration[i].x = count > 0 ? ax / count : 0.0f;
g_wwi.acceleration[i].y = count > 0 ? ay / count : 0.0f;
}
}
HeightRingLinearMean(g_wwi.acceleration, g_wwi.buffer);
for (int i = 0; i < 16; ++i) {
Pair acc = g_wwi.acceleration[i];
if (fabsf(acc.x) < g_params.minAcc) acc.x = 0; if (fabsf(acc.y) < g_params.minAcc) acc.y = 0;
if (fabsf(acc.x) > g_params.maxAcc) acc.x = acc.x > 0 ? g_params.maxAcc : -g_params.maxAcc;
if (fabsf(acc.y) > g_params.maxAcc) acc.y = acc.y > 0 ? g_params.maxAcc : -g_params.maxAcc;
g_wwi.velocity[i].x = acc.x * time + g_wwi.velocity[i].x * g_params.drag;
g_wwi.velocity[i].y = acc.y * time + g_wwi.velocity[i].y * g_params.drag;
acc_sum += fabsf(acc.x) + fabsf(acc.y);
}
HeightRingLinearMean(g_wwi.velocity, g_wwi.buffer);
for (int i = 0; i < 16; ++i) {
Pair vel = g_wwi.velocity[i];
if (fabsf(vel.x) < g_params.minVelocity) vel.x = 0; if (fabsf(vel.y) < g_params.minVelocity) vel.y = 0;
if (fabsf(vel.x) > g_params.maxVelocity) vel.x = vel.x > 0 ? g_params.maxVelocity : -g_params.maxVelocity;
if (fabsf(vel.y) > g_params.maxVelocity) vel.y = vel.y > 0 ? g_params.maxVelocity : -g_params.maxVelocity;
g_wwi.position[i].x += vel.x * time * g_params.moveFactor;
g_wwi.position[i].y += vel.y * time * g_params.moveFactor;
vel_sum += fabsf(vel.x) + fabsf(vel.y);
}
if (!g_wwi.can_wobble_top) { for (int i = 0; i < 4; ++i) for (int j = 0; j < 3; ++j) g_wwi.position[i + 4 * j].y = g_wwi.origin[i + 4 * j].y; }
if (!g_wwi.can_wobble_bottom) { for (int i = 12; i < 16; ++i) for (int j = 0; j < 3; ++j) g_wwi.position[i - 4 * j].y = g_wwi.origin[i - 4 * j].y; }
if (!g_wwi.can_wobble_left) { for (int i = 0; i < 16; i += 4) for (int j = 0; j < 3; ++j) g_wwi.position[i + j].x = g_wwi.origin[i + j].x; }
if (!g_wwi.can_wobble_right) { for (int i = 3; i < 16; i += 4) for (int j = 0; j < 3; ++j) g_wwi.position[i - j].x = g_wwi.origin[i - j].x; }
g_wwi.wobblying = !(acc_sum < g_params.stopAcc && vel_sum < g_params.stopVelocity);
}
static Pair ComputeBezierPoint(float tx, float ty) {
float px[4] = { (1 - tx)*(1 - tx)*(1 - tx), 3*(1 - tx)*(1 - tx)*tx, 3*(1 - tx)*tx*tx, tx*tx*tx };
float py[4] = { (1 - ty)*(1 - ty)*(1 - ty), 3*(1 - ty)*(1 - ty)*ty, 3*(1 - ty)*ty*ty, ty*ty*ty };
Pair res = { 0.0f, 0.0f };
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 4; ++i) {
res.x += px[i] * py[j] * g_wwi.position[i + j * 4].x;
res.y += px[i] * py[j] * g_wwi.position[i + j * 4].y;
}
}
return res;
}
static void DrawOverlayFrameD2D() {
if (!g_wobblyRT || !g_wobblyBrush || !g_wobblyMemDC) return;
RECT bindRect = { 0, 0, g_screenW, g_screenH };
g_wobblyRT->BindDC(g_wobblyMemDC, &bindRect);
g_wobblyRT->BeginDraw();
g_wobblyRT->Clear(D2D1::ColorF(0, 0, 0, 0));
g_wobblyRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
float vLeft = (float)g_screenX;
float vTop = (float)g_screenY;
ID2D1PathGeometry* outlineGeo = nullptr;
g_d2dFactory->CreatePathGeometry(&outlineGeo);
if (outlineGeo) {
ID2D1GeometrySink* sink = nullptr;
outlineGeo->Open(&sink);
Pair startP = ComputeBezierPoint(0.0f, 0.0f);
sink->BeginFigure(D2D1::Point2F(startP.x - vLeft, startP.y - vTop), D2D1_FIGURE_BEGIN_FILLED);
for (int x = 1; x <= X_TILES; x++) {
Pair p = ComputeBezierPoint((float)x / X_TILES, 0.0f);
sink->AddLine(D2D1::Point2F(p.x - vLeft, p.y - vTop));
}
for (int y = 1; y <= Y_TILES; y++) {
Pair p = ComputeBezierPoint(1.0f, (float)y / Y_TILES);
sink->AddLine(D2D1::Point2F(p.x - vLeft, p.y - vTop));
}
for (int x = X_TILES - 1; x >= 0; x--) {
Pair p = ComputeBezierPoint((float)x / X_TILES, 1.0f);
sink->AddLine(D2D1::Point2F(p.x - vLeft, p.y - vTop));
}
for (int y = Y_TILES - 1; y >= 0; y--) {
Pair p = ComputeBezierPoint(0.0f, (float)y / Y_TILES);
sink->AddLine(D2D1::Point2F(p.x - vLeft, p.y - vTop));
}
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
sink->Close();
ID2D1Layer* layer = nullptr;
g_wobblyRT->CreateLayer(&layer);
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
layerParams.geometricMask = outlineGeo;
layerParams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
g_wobblyRT->PushLayer(&layerParams, layer);
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
float tx1 = (float)x / X_TILES, ty1 = (float)y / Y_TILES;
float tx2 = (float)(x + 1) / X_TILES, ty2 = (float)(y + 1) / Y_TILES;
Pair bp1 = ComputeBezierPoint(tx1, ty1);
Pair bp2 = ComputeBezierPoint(tx2, ty1);
Pair bp3 = ComputeBezierPoint(tx1, ty2);
Pair bp4 = ComputeBezierPoint(tx2, ty2);
D2D1_POINT_2F p1 = D2D1::Point2F(bp1.x - vLeft, bp1.y - vTop);
D2D1_POINT_2F p2 = D2D1::Point2F(bp2.x - vLeft, bp2.y - vTop);
D2D1_POINT_2F p3 = D2D1::Point2F(bp3.x - vLeft, bp3.y - vTop);
D2D1_POINT_2F p4 = D2D1::Point2F(bp4.x - vLeft, bp4.y - vTop);
D2D1_POINT_2F c = D2D1::Point2F((p1.x+p2.x+p3.x+p4.x)/4.0f, (p1.y+p2.y+p3.y+p4.y)/4.0f);
ID2D1PathGeometry* quadGeo = CreateQuadGeo(g_d2dFactory,
BloatPoint(p1, c), BloatPoint(p2, c), BloatPoint(p4, c), BloatPoint(p3, c));
if (quadGeo) {
float sx = (float)g_capX + tx1 * g_capW;
float sy = (float)g_capY + ty1 * g_capH;
float sw = (float)g_capW / X_TILES;
float sh = (float)g_capH / Y_TILES;
float m11 = (p2.x - p1.x) / sw;
float m12 = (p2.y - p1.y) / sw;
float m21 = (p3.x - p1.x) / sh;
float m22 = (p3.y - p1.y) / sh;
float m31 = p1.x - sx * m11 - sy * m21;
float m32 = p1.y - sx * m12 - sy * m22;
g_wobblyBrush->SetTransform(D2D1::Matrix3x2F(m11, m12, m21, m22, m31, m32));
g_wobblyRT->FillGeometry(quadGeo, g_wobblyBrush);
quadGeo->Release();
}
}
}
g_wobblyRT->PopLayer();
if (layer) layer->Release();
outlineGeo->Release();
}
g_wobblyRT->EndDraw();
HDC hdcScreen = GetDC(NULL);
POINT ptS = {0, 0};
POINT ptW = {g_screenX, g_screenY};
SIZE sz = {g_screenW, g_screenH};
BLENDFUNCTION bl = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
UpdateLayeredWindow(g_overlayHwnd, hdcScreen, &ptW, &sz, g_wobblyMemDC, &ptS, 0, &bl, ULW_ALPHA);
ReleaseDC(NULL, hdcScreen);
}
static void CleanupWobblyD2D() {
if (g_wobblyBrush) { g_wobblyBrush->Release(); g_wobblyBrush = nullptr; }
}
static LRESULT CALLBACK OverlayProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_CLOSE) {
KillTimer(hwnd, 1);
DestroyWindow(hwnd);
return 0;
} else if (msg == WM_TIMER) {
if (g_isUnloading.load(std::memory_order_relaxed)) {
KillTimer(hwnd, 1);
return 0;
}
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
if (g_isMoving || g_isSettling) {
if (!IsWindow(g_mainHwnd)) {
g_isMoving = false;
g_isSettling = false;
KillTimer(hwnd, 1);
ShowWindow(hwnd, SW_HIDE);
CleanupWobblyD2D();
g_mainHwnd = NULL;
return 0;
}
if (!(GetWindowLongPtrW(g_mainHwnd, GWL_EXSTYLE) & WS_EX_LAYERED)) {
SetWindowLongPtrW(g_mainHwnd, GWL_EXSTYLE, g_oldExStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(g_mainHwnd, 0, 1, LWA_ALPHA);
}
DWORD now = GetTickCount();
DWORD delta = now - g_lastTick;
g_lastTick = now;
if (delta > 64) delta = 64;
while (delta > 0) {
DWORD dt = delta > 10 ? 10 : delta;
StepPhysics((float)dt);
delta -= dt;
}
DrawOverlayFrameD2D();
if (g_isSettling && !g_wwi.wobblying) {
g_isSettling = false;
KillTimer(hwnd, 1);
ShowWindow(hwnd, SW_HIDE);
SetLayeredWindowAttributes(g_mainHwnd, 0, 255, LWA_ALPHA);
if (!(g_oldExStyle & WS_EX_LAYERED)) {
SetWindowLongPtrW(g_mainHwnd, GWL_EXSTYLE, g_oldExStyle);
}
CleanupWobblyD2D();
g_mainHwnd = NULL;
}
}
return 0;
}
return DefWindowProc(hwnd, msg, wp, lp);
}
static void InitializeWobbly(void) {
if (g_overlayHwnd) return;
if (!g_hInstance) {
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)&OverlayProc, (HMODULE*)&g_hInstance);
}
WNDCLASSA oc = {0};
oc.lpfnWndProc = OverlayProc;
oc.hInstance = g_hInstance;
oc.lpszClassName = "WobblyOverlayWindow";
RegisterClassA(&oc);
g_screenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
g_screenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
g_screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
g_screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
g_overlayHwnd = CreateWindowExA(
WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
"WobblyOverlayWindow", NULL, WS_POPUP,
g_screenX, g_screenY, g_screenW, g_screenH,
NULL, NULL, oc.hInstance, NULL
);
}
static void OnEnterSizeMove(HWND hwnd) {
if (!g_wobblyEnabled.load(std::memory_order_relaxed)) return;
if (IsZoomed(hwnd) || IsIconic(hwnd)) return;
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
if ((g_isMoving || g_isSettling) && g_mainHwnd != NULL && g_mainHwnd != hwnd) return;
if (!g_engineInitialized) {
InitializeWobbly();
g_engineInitialized = true;
}
g_mainHwnd = hwnd;
if (!g_isSettling && !g_isMoving) {
CaptureWindowForWobbly(hwnd);
g_oldExStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, g_oldExStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(hwnd, 0, 1, LWA_ALPHA);
RECT rcExt;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcExt, sizeof(rcExt)) != S_OK) GetWindowRect(hwnd, &rcExt);
InitWobblyInfo((float)rcExt.left, (float)rcExt.top, (float)(rcExt.right - rcExt.left), (float)(rcExt.bottom - rcExt.top));
} else {
RECT rcExt;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcExt, sizeof(rcExt)) != S_OK) GetWindowRect(hwnd, &rcExt);
UpdateWobblyOrigin((float)rcExt.left, (float)rcExt.top, (float)(rcExt.right - rcExt.left), (float)(rcExt.bottom - rcExt.top));
for (int i = 0; i < 16; ++i) g_wwi.constraint[i] = false;
}
RECT rcExt;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcExt, sizeof(rcExt)) != S_OK) GetWindowRect(hwnd, &rcExt);
POINT pt;
GetCursorPos(&pt);
float x_inc = (rcExt.right - rcExt.left) / 3.0f;
float y_inc = (rcExt.bottom - rcExt.top) / 3.0f;
int indx = (int)((pt.x - rcExt.left) / x_inc + 0.5f);
int indy = (int)((pt.y - rcExt.top) / y_inc + 0.5f);
if (indx < 0) indx = 0; if (indx > 3) indx = 3;
if (indy < 0) indy = 0; if (indy > 3) indy = 3;
g_wwi.constraint[indy * 4 + indx] = true;
g_isMoving = true;
g_isSettling = false;
g_lastTick = GetTickCount();
ShowWindow(g_overlayHwnd, SW_SHOWNOACTIVATE);
SetTimer(g_overlayHwnd, 1, 16, NULL);
}
static void OnSizingMoving(HWND hwnd, LPRECT r) {
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
if (g_isMoving && g_mainHwnd == hwnd) {
RECT rExt, rWin;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rExt, sizeof(rExt)) != S_OK) GetWindowRect(hwnd, &rExt);
GetWindowRect(hwnd, &rWin);
int dx = rExt.left - rWin.left;
int dy = rExt.top - rWin.top;
float w = (float)(r->right - r->left) - (rWin.right - rExt.right) - dx;
float h = (float)(r->bottom - r->top) - (rWin.bottom - rExt.bottom) - dy;
UpdateWobblyOrigin((float)r->left + dx, (float)r->top + dy, w, h);
}
}
static void OnWindowPosChanged(HWND hwnd, WINDOWPOS* wp) {
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
if (hwnd != g_mainHwnd || g_isMoving) return;
if (g_isSettling && wp) {
if (wp->flags & SWP_NOMOVE && wp->flags & SWP_NOSIZE) return;
RECT rExt, rWin;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rExt, sizeof(rExt)) == S_OK && GetWindowRect(hwnd, &rWin)) {
int dx = rExt.left - rWin.left;
int dy = rExt.top - rWin.top;
float w = (float)wp->cx - (rWin.right - rExt.right) - dx;
float h = (float)wp->cy - (rWin.bottom - rExt.bottom) - dy;
UpdateWobblyOrigin((float)wp->x + dx, (float)wp->y + dy, w, h);
}
}
}
static void OnExitSizeMove(HWND hwnd) {
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
if (g_isMoving && g_mainHwnd == hwnd) {
g_isMoving = false;
g_isSettling = true;
RECT rcExt;
if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcExt, sizeof(rcExt)) != S_OK) GetWindowRect(hwnd, &rcExt);
UpdateWobblyOrigin((float)rcExt.left, (float)rcExt.top, (float)(rcExt.right - rcExt.left), (float)(rcExt.bottom - rcExt.top));
}
}
// -------------------------------------------------------------------------
// Hooks & Subclassing
// -------------------------------------------------------------------------
LRESULT CALLBACK WobblySubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
if (msg == g_wmDetach) {
RemoveWindowSubclass(hwnd, WobblySubclassProc, uIdSubclass);
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
else if (msg == WM_ENTERSIZEMOVE) {
OnEnterSizeMove(hwnd);
}
else if (msg == WM_SIZING || msg == WM_MOVING) {
OnSizingMoving(hwnd, (LPRECT)lParam);
}
else if (msg == WM_WINDOWPOSCHANGED) {
OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam);
}
else if (msg == WM_EXITSIZEMOVE) {
OnExitSizeMove(hwnd);
}
else if (msg == WM_NCDESTROY) {
RemoveWindowSubclass(hwnd, WobblySubclassProc, uIdSubclass);
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
static bool IsInternalModClass(HWND hwnd) {
char className[256];
if (GetClassNameA(hwnd, className, sizeof(className))) {
if (strcmp(className, "GhostWindowClass") == 0 ||
strcmp(className, "WobblyOverlayWindow") == 0) {
return true;
}
}
return false;
}
HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) {
HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
if (hwnd && (dwStyle & WS_CHILD) == 0 && !IsInternalModClass(hwnd)) SetWindowSubclass(hwnd, WobblySubclassProc, 0, 0);
return hwnd;
}
HWND WINAPI CreateWindowExA_Hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) {
HWND hwnd = CreateWindowExA_Original(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
if (hwnd && (dwStyle & WS_CHILD) == 0 && !IsInternalModClass(hwnd)) SetWindowSubclass(hwnd, WobblySubclassProc, 0, 0);
return hwnd;
}
static LRESULT CALLBACK CrossThreadHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
CWPSTRUCT* pCwp = (CWPSTRUCT*)lParam;
if (pCwp->message == g_wmAttach) SetWindowSubclass(pCwp->hwnd, WobblySubclassProc, 0, 0);
else if (pCwp->message == g_wmDetach) RemoveWindowSubclass(pCwp->hwnd, WobblySubclassProc, 0);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
static BOOL CALLBACK AttachSubclassEnumProc(HWND hwnd, LPARAM lParam) {
LONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE);
if ((style & WS_CHILD) == 0) {
DWORD tid = GetWindowThreadProcessId(hwnd, NULL);
HHOOK hHook = SetWindowsHookEx(WH_CALLWNDPROC, CrossThreadHookProc, NULL, tid);
if (hHook) {
SendMessageTimeoutW(hwnd, g_wmAttach, 0, 0, SMTO_ABORTIFHUNG | SMTO_NORMAL, 500, NULL);
UnhookWindowsHookEx(hHook);
}
}
return TRUE;
}
static BOOL CALLBACK DetachSubclassEnumProc(HWND hwnd, LPARAM lParam) {
LONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE);
if ((style & WS_CHILD) == 0) {
SendMessageTimeoutW(hwnd, g_wmDetach, 0, 0, SMTO_ABORTIFHUNG | SMTO_NORMAL, 100, NULL);
}
return TRUE;
}
void EnumerateAndSubclass(BOOL attach) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
THREADENTRY32 te;
te.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnapshot, &te)) {
DWORD pid = GetCurrentProcessId();
do {
if (te.th32OwnerProcessID == pid) {
EnumThreadWindows(te.th32ThreadID, attach ? AttachSubclassEnumProc : DetachSubclassEnumProc, 0);
}
} while (Thread32Next(hSnapshot, &te));
}
CloseHandle(hSnapshot);
}
}
BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) {
if (GetAncestor(hWnd, GA_ROOT) != hWnd) {
return ShowWindow_Original(hWnd, nCmdShow);
}
if (nCmdShow == SW_MINIMIZE || nCmdShow == SW_SHOWMINIMIZED || nCmdShow == SW_SHOWMINNOACTIVE) {
if (g_d2dFactory) {
SetDwmTransitions(hWnd, FALSE);
GhostAnimData* data = PrepareGenieAnim(hWnd, FALSE);
if (data) {
ShowGhostSync(data);
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
SpawnAnimationThread(data);
}
}
return ShowWindow_Original(hWnd, nCmdShow);
}
else if (nCmdShow == SW_RESTORE || nCmdShow == SW_SHOWNORMAL) {
if (IsIconic(hWnd) && g_d2dFactory) {
SetDwmTransitions(hWnd, FALSE);
GhostAnimData* data = PrepareGenieAnim(hWnd, TRUE);
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
BOOL res = ShowWindow_Original(hWnd, nCmdShow);
if (data) SpawnAnimationThread(data);
return res;
}
}
return ShowWindow_Original(hWnd, nCmdShow);
}
LRESULT WINAPI DefWindowProcW_Hook(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
if (Msg == WM_DESTROY) {
std::lock_guard<std::mutex> lock(g_CacheMutex);
if (g_SnapshotCache.count(hWnd)) {
DeleteObject(g_SnapshotCache[hWnd].hBmp);
g_SnapshotCache.erase(hWnd);
}
if (g_IconPositions.count(hWnd)) {
g_IconPositions.erase(hWnd);
}
if (g_RectCache.count(hWnd)) {
g_RectCache.erase(hWnd);
}
}
if (Msg == WM_SYSCOMMAND) {
UINT cmd = wParam & 0xFFF0;
if (cmd == SC_MINIMIZE) {
if (GetAncestor(hWnd, GA_ROOT) != hWnd) {
return DefWindowProcW_Original(hWnd, Msg, wParam, lParam);
}
if (g_d2dFactory) {
SetDwmTransitions(hWnd, FALSE);
GhostAnimData* data = PrepareGenieAnim(hWnd, FALSE);
if (data) {
ShowGhostSync(data);
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
SpawnAnimationThread(data);
}
}
return DefWindowProcW_Original(hWnd, Msg, wParam, lParam);
}
else if (cmd == SC_RESTORE && IsIconic(hWnd)) {
if (GetAncestor(hWnd, GA_ROOT) != hWnd) {
return DefWindowProcW_Original(hWnd, Msg, wParam, lParam);
}
if (g_d2dFactory) {
SetDwmTransitions(hWnd, FALSE);
GhostAnimData* data = PrepareGenieAnim(hWnd, TRUE);
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
LRESULT res = DefWindowProcW_Original(hWnd, Msg, wParam, lParam);
if (data) SpawnAnimationThread(data);
return res;
}
}
}
return DefWindowProcW_Original(hWnd, Msg, wParam, lParam);
}
// -------------------------------------------------------------------------
// Mod Lifecycle
// -------------------------------------------------------------------------
BOOL Wh_ModInit() {
Wh_Log(L"Custom Animations & Wobbly Mod Loading...");
LoadSettings();
if (!g_hInstance) {
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)&Wh_ModInit, (HMODULE*)&g_hInstance);
}
// ADD THIS: Register the Ghost window class
WNDCLASSA gc = {0};
gc.lpfnWndProc = GhostProc;
gc.hInstance = g_hInstance;
gc.lpszClassName = "GhostWindowClass";
RegisterClassA(&gc);
g_isUnloading.store(false, std::memory_order_relaxed);
g_engineInitialized = false;
g_isMoving = false;
g_isSettling = false;
g_mainHwnd = NULL;
g_overlayHwnd = NULL;
g_lastTick = 0;
WCHAR exePath[MAX_PATH];
if (GetModuleFileNameW(NULL, exePath, MAX_PATH)) {
_wcslwr_s(exePath);
if (wcsstr(exePath, L"dwm.exe") || wcsstr(exePath, L"csrss.exe") ||
wcsstr(exePath, L"lsass.exe") || wcsstr(exePath, L"winlogon.exe")) {
return FALSE;
}
}
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory), reinterpret_cast<void**>(&g_d2dFactory));
if (FAILED(hr)) { g_d2dFactory = nullptr; }
g_wmAttach = RegisterWindowMessageW(L"WobblyWindows_Attach");
g_wmDetach = RegisterWindowMessageW(L"WobblyWindows_Detach");
Wh_SetFunctionHook((void*)DefWindowProcW, (void*)DefWindowProcW_Hook, (void**)&DefWindowProcW_Original);
Wh_SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original);
Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original);
Wh_SetFunctionHook((void*)CreateWindowExA, (void*)CreateWindowExA_Hook, (void**)&CreateWindowExA_Original);
EnumerateAndSubclass(TRUE);
return TRUE;
}
void Wh_ModSettingsChanged() {
LoadSettings();
}
void StopAndJoinAllThreads() {
g_isUnloading.store(true, std::memory_order_relaxed);
std::vector<HANDLE> threadsToJoin;
{
std::lock_guard<std::mutex> lock(g_threadsMutex);
threadsToJoin = g_animationThreads;
g_animationThreads.clear();
}
for (HANDLE hThread : threadsToJoin) {
while (true) {
DWORD res = MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE, QS_ALLINPUT);
if (res == WAIT_OBJECT_0) {
break;
} else if (res == WAIT_OBJECT_0 + 1) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else {
break;
}
}
CloseHandle(hThread);
}
std::vector<HWND> ghostsToClose;
{
std::lock_guard<std::mutex> lock(g_ghostMutex);
ghostsToClose = g_activeGhosts;
}
for (HWND hwnd : ghostsToClose) {
if (IsWindow(hwnd)) {
SendMessageTimeoutW(hwnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 200, NULL);
}
}
}
void Wh_ModUninit() {
Wh_Log(L"Custom Animations & Wobbly Mod Unloading...");
EnumerateAndSubclass(FALSE);
StopAndJoinAllThreads();
if (g_engineInitialized.exchange(false, std::memory_order_seq_cst)) {
if (g_overlayHwnd) {
SendMessageTimeoutW(g_overlayHwnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG | SMTO_NORMAL, 500, NULL);
g_overlayHwnd = NULL;
}
std::lock_guard<std::recursive_mutex> lock(g_wobblyMutex);
CleanupWobblyD2D();
if (g_wobblyRT) { g_wobblyRT->Release(); g_wobblyRT = nullptr; }
if (g_wobblyMemDC) { DeleteDC(g_wobblyMemDC); g_wobblyMemDC = NULL; }
if (g_wobblyTargetBmp) { DeleteObject(g_wobblyTargetBmp); g_wobblyTargetBmp = NULL; }
}
if (g_hInstance) {
UnregisterClassA("WobblyOverlayWindow", g_hInstance);
UnregisterClassA("GhostWindowClass", g_hInstance);
g_hInstance = NULL;
}
if (g_d2dFactory) {
g_d2dFactory->Release();
g_d2dFactory = nullptr;
}
std::lock_guard<std::mutex> lock(g_CacheMutex);
for (auto& pair : g_SnapshotCache) {
DeleteObject(pair.second.hBmp);
}
g_SnapshotCache.clear();
g_IconPositions.clear();
g_RectCache.clear();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment