Skip to content

Instantly share code, notes, and snippets.

@valinet
Last active October 12, 2023 01:31
Show Gist options
  • Save valinet/bfc3e15d0d999cd3d2aab8d554955e6f to your computer and use it in GitHub Desktop.
Save valinet/bfc3e15d0d999cd3d2aab8d554955e6f to your computer and use it in GitHub Desktop.
An example showing an interesting technique necessary to toggle the clock flyout in the Windows 10 taskbar on the monitor containing the mouse
/*
* Example showing an interesting technique necessary to toggle the clock flyout
* in the Windows 10 taskbar on the monitor containing the mouse
*
* Copyright (C) 2006-2021 VALINET Solutions SRL. All rights reserved.
* License: GPLv2
*/
#include <Windows.h>
#include <TlHelp32.h>
typedef struct _ClockButton_ToggleFlyoutCallback_Params
{
BOOL bShouldQuit;
HANDLE hProcess;
void* TrayUIInstance;
unsigned int CLOCKBUTTON_OFFSET_IN_TRAYUI;
void* oldClockButtonInstance;
} ClockButton_ToggleFlyoutCallback_Params;
HWND GetTaskbarFromMonitorWhichContainsMouse(POINT ptCursor, DWORD dwFlags)
{
HMONITOR hMonitor = MonitorFromPoint(ptCursor, dwFlags);
HWND hWnd = NULL;
do
{
hWnd = FindWindowEx(
NULL,
hWnd,
L"Shell_SecondaryTrayWnd",
NULL
);
if (MonitorFromWindow(hWnd, dwFlags) == hMonitor)
{
break;
}
} while (hWnd);
if (!hWnd)
{
hWnd = FindWindowEx(
NULL,
NULL,
L"Shell_TrayWnd",
NULL
);
}
return hWnd;
}
void ClockButton_ToggleFlyoutCallback(
HWND hWnd,
UINT uMsg,
ClockButton_ToggleFlyoutCallback_Params* params,
LRESULT lRes
)
{
SIZE_T dwNumberOfBytesWritten = 0;
WriteProcessMemory(
params->hProcess,
((INT64*)params->TrayUIInstance + params->CLOCKBUTTON_OFFSET_IN_TRAYUI),
&(params->oldClockButtonInstance),
sizeof(uintptr_t),
&dwNumberOfBytesWritten
);
CloseHandle(params->hProcess);
if (params->bShouldQuit)
{
PostQuitMessage(0);
}
free(params);
}
BOOL ToggleClockFlyout(HWND hWndTaskbar, BOOL bShouldQuit)
{
// This magic value comes from `explorer.exe`, method `TrayUI::WndProc`
const unsigned int WM_TOGGLE_CLOCK_FLYOUT = 1486;
HWND hShellTray_Wnd = FindWindowExW(NULL, NULL, L"Shell_TrayWnd", NULL);
if (hWndTaskbar == hShellTray_Wnd)
{
// On the main monitor, the TrayUI component of CTray handles this
// message and basically does a `ClockButton::ToggleFlyout`; that's
// the only place in code where that is used, otherwise, clicking and
// dismissing the clock flyout probably involves 2 separate methods
if (bShouldQuit)
{
PostQuitMessage(0);
}
return PostMessageW(hShellTray_Wnd, WM_TOGGLE_CLOCK_FLYOUT, 0, 0);
}
// Of course, on secondary monitors, the situation is much more
// complicated; there is no simple way to do this, afaik; the way I do it
// is to obtain a pointer to TrayUI from CTray (pointers to the classes
// that created the windows are always available at location 0 in the hWnd)
// and from there issue a "show clock flyout" message manually, taking care to temporarly
// change the internal clock button pointer of the class to point
// to the clock button on the secondary monitor.
HWND hWnd = FindWindowExW(hWndTaskbar, NULL, L"ClockButton", NULL);
if (hWnd)
{
HANDLE hProcess = NULL, hSnapshot = NULL;
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS,
0
);
if (Process32First(hSnapshot, &pe32) == TRUE)
{
do
{
if (!wcscmp(pe32.szExeFile, L"explorer.exe"))
{
hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pe32.th32ProcessID);
break;
}
} while (Process32Next(hSnapshot, &pe32) == TRUE);
}
CloseHandle(hSnapshot);
if (!hProcess)
{
return FALSE;
}
INT64* CTrayInstance = (BYTE*)(GetWindowLongPtrW(hShellTray_Wnd, 0)); // -> CTray
void* ClockButtonInstance = (BYTE*)(GetWindowLongPtrW(hWnd, 0)); // -> ClockButton
// These offsets can be obtained by inspecting `explorer.exe`,
// specifically the methods `CTray::v_WndProc` and `TrayUI::WndProc`
const unsigned int TRAYUI_OFFSET_IN_CTRAY = 110;
const unsigned int CLOCKBUTTON_OFFSET_IN_TRAYUI = 100;
SIZE_T dwNumberOfBytesRead = 0;
void* TrayUIInstance = NULL;
ReadProcessMemory(
hProcess,
((INT64*)CTrayInstance + TRAYUI_OFFSET_IN_CTRAY),
&TrayUIInstance,
sizeof(uintptr_t),
&dwNumberOfBytesRead
);
if (dwNumberOfBytesRead == sizeof(uintptr_t) && TrayUIInstance)
{
dwNumberOfBytesRead = 0;
void* oldClockButtonInstance = NULL;
ReadProcessMemory(
hProcess,
((INT64*)TrayUIInstance + CLOCKBUTTON_OFFSET_IN_TRAYUI),
&oldClockButtonInstance,
sizeof(uintptr_t),
&dwNumberOfBytesRead
);
if (dwNumberOfBytesRead == sizeof(uintptr_t) && oldClockButtonInstance)
{
ClockButton_ToggleFlyoutCallback_Params* params = malloc(sizeof(ClockButton_ToggleFlyoutCallback_Params));
if (params)
{
SIZE_T dwNumberOfBytesWritten = 0;
WriteProcessMemory(
hProcess,
((INT64*)TrayUIInstance + CLOCKBUTTON_OFFSET_IN_TRAYUI),
&ClockButtonInstance,
sizeof(uintptr_t),
&dwNumberOfBytesWritten
);
if (dwNumberOfBytesWritten == sizeof(uintptr_t))
{
params->bShouldQuit = bShouldQuit;
params->TrayUIInstance = TrayUIInstance;
params->CLOCKBUTTON_OFFSET_IN_TRAYUI = CLOCKBUTTON_OFFSET_IN_TRAYUI;
params->oldClockButtonInstance = oldClockButtonInstance;
params->hProcess = hProcess;
// Unfortunately, it does not work with a simple SendMessageW
if (!SendMessageCallbackW(
hShellTray_Wnd,
WM_TOGGLE_CLOCK_FLYOUT,
0,
0,
ClockButton_ToggleFlyoutCallback,
params
))
{
CloseHandle(hProcess);
free(params);
}
else
{
return TRUE;
}
}
}
}
}
}
return FALSE;
}
int main()
{
POINT ptCursor;
GetCursorPos(&ptCursor);
HWND hTaskbarWithMouse = GetTaskbarFromMonitorWhichContainsMouse(ptCursor, MONITOR_DEFAULTTOPRIMARY);
MSG Msg;
HANDLE hWaites[2];
hWaites[0] = CreateThread(0, 0, Sleep, 3000, 0, 0);
if (!hWaites[0])
{
return 0;
}
hWaites[1] = CreateThread(0, 0, Sleep, 0, 0, 0);
if (!hWaites[1])
{
return 0;
}
unsigned int events = 2;
BOOL bDone = FALSE;
while (!bDone)
{
DWORD dwRes = MsgWaitForMultipleObjectsEx(
events,
hWaites,
INFINITE,
QS_ALLINPUT,
0
);
if (dwRes == WAIT_OBJECT_0 + events)
{
if (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE)) {
if (Msg.message == WM_QUIT)
{
bDone = TRUE;
}
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
else
{
if (hWaites[events - 1])
{
CloseHandle(hWaites[events - 1]);
}
hWaites[events - 1] = NULL;
events--;
ToggleClockFlyout(hTaskbarWithMouse, !events);
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment