0

I'd like to put a button in a foreign windows' title bar, much like Teamviewer does with the Quickconnect feature, or like Chrome has one in the top-right for switching users.

I know this is a repeat of How is Teamviewers Quickconnect button accomplished?

I'm just wondering if it would be possible to get a working example or a link to an open-source program that implements this. The answers given there are rather advanced for me. As in, how am I supposed to "hook" and "intercept" WM_NCPAINT message and so on.

Community
  • 1
  • 1
user1340531
  • 720
  • 1
  • 10
  • 21

1 Answers1

2

This is the most simple example i can develop:

You need Visual Studio, add 2 project to the solution:

enter image description here

first project (HookDLL) is a dll project, second (Running app) is a win32 console project

in main.cpp (at project Running app) add this:

__declspec(dllimport) void RunHook();

int _tmain(int argc, _TCHAR* argv[])
{
    RunHook();
    return 0;
}

in dllmain button.cpp (at HookDLL project) add this code:

#include <Windows.h>
#include <stdio.h>

HINSTANCE hinstDLL; 
HHOOK hhook_wndproc; 
HWND b = NULL;
HBRUSH blue_brush = NULL, yellow_brush, red_brush;
int button_status = 0;

LRESULT CALLBACK DefaultWindowProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    {
    case WM_CREATE:
        if(!blue_brush)
        {
            blue_brush = CreateSolidBrush(RGB(0, 0, 255));
            yellow_brush = CreateSolidBrush(RGB(255, 255, 0));
            red_brush = CreateSolidBrush(RGB(255, 0, 0));

        }
        break;
    case WM_PAINT:
        {
            HBRUSH b;
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            switch(button_status)
            {
            case 0:
                b = blue_brush;
                break;
            case 1:
                b = yellow_brush;
                break;
            default:
                b = red_brush;
            }
            FillRect(hdc, &ps.rcPaint, b);
            EndPaint(hwnd, &ps);
        }
        return 0;
    case WM_MOUSEMOVE:
        if(button_status == 0)
        {
            SetTimer(hwnd, 1, 100, NULL);
            button_status = 1;
            InvalidateRect(hwnd, NULL, false);
        }
        return 0;
    case WM_TIMER:
        {
            POINT pt;
            GetCursorPos(&pt);
            if(button_status == 1 && WindowFromPoint(pt) != hwnd)
            {
                KillTimer(hwnd, 1);
                button_status = 0;
                InvalidateRect(hwnd, NULL, false);
            }
        }
        return 0;
    case WM_MOUSELEAVE:
        button_status = 0;
        InvalidateRect(hwnd, NULL, false);
        return 0;
    case WM_LBUTTONDOWN:
        button_status = 2;
        InvalidateRect(hwnd, NULL, false);
        return 0;
    case WM_LBUTTONUP:
        if(button_status == 2) MessageBox(GetParent(hwnd), "teamviewer like button clicked", "Message", MB_OK);
        button_status = 1;
        InvalidateRect(hwnd, NULL, false);
        return 0;
    }
    return DefWindowProc(hwnd, Msg, wParam, lParam);
}

void InitButton(HWND parent, int xPos, int yPos)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = DefaultWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstDLL;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "DEFAULT_CLASS";
    RegisterClass(&wc);
    b = CreateWindowEx(WS_EX_TOOLWINDOW, "DEFAULT_CLASS", NULL, WS_BORDER | WS_POPUP | WS_VISIBLE, xPos, yPos, 20, 20, parent, NULL, hinstDLL, NULL);
}

LRESULT WINAPI HookCallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode >= 0 && lParam != 0)
    { 
        CWPRETSTRUCT *msg = (CWPRETSTRUCT*)lParam;
        if(!IsWindow(msg->hwnd) || (GetWindowLong(msg->hwnd, GWL_STYLE) & WS_CHILD) != 0) return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
        switch(msg->message)
        {
        case WM_SHOWWINDOW:
            if(!b && msg->wParam != 0)
            {
                b = (HWND)1;// see NOTES 5
                RECT a;
                GetWindowRect(msg->hwnd, &a);
                InitButton(msg->hwnd, a.right - 150, a.top);
            }
            break;
        case WM_SIZE:
            if(GetParent(b) == msg->hwnd)
            {
                RECT a;
                GetWindowRect(msg->hwnd, &a);
                SetWindowPos(b, 0, a.right - 150, a.top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
            }
            break;
        case WM_SIZING: 
        case WM_MOVING:
            if(GetParent(b) == msg->hwnd)
            {
                RECT* lprc = (LPRECT) msg->lParam;
                SetWindowPos(b, 0, lprc->right - 150, lprc->top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
            }
        }
    }
    return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
}

__declspec(dllexport) void RunHook()
{
    hhook_wndproc = SetWindowsHookEx(WH_CALLWNDPROCRET, HookCallWndProc, hinstDLL, 0);
    char aux[10];
    gets_s(aux);
    UnhookWindowsHookEx(hhook_wndproc);
}

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        hinstDLL = hModule;
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Now, make dll project depedent of running app project in project->project dependencies:

enter image description here

NOTES: 1) i dont use NC paint code, because not always works, if windows buffer non client region, erases customized NC paint buttons

2) in 64 bits enviroment, you need to run a 32 bits hook for 32 bits apps, and other hook for 64 bits apps

3) YOU CAN NOT DEBUG YOUR HOOK WHEN IS CONNECTED TO ANOTHER PROCCESS, i suggest you debug it with a windows in your app and thread, and test it late in another proccess when is working

4) i use a button like approach for simplicity

5) this line

b = (HWND)1;

I use it for "solve" a multi thread problem, i suggest you make better code (syncronization) this case

HOW THIS WORKS:

  • run the app
  • when it start install a hook
  • open any other app (same 32/64 bits, see NOTE 2)
  • you must see a blue button at left side of title bar
  • click it and see a message box
  • for finish hook: just press ENTER at console window

CODE FLOW:

  • Running app just calls RunHook() procedure in dll, and dll do the work
  • RunHook() in dll starts a hook HookCallWndProc (global)
  • HookCallWndProc captures required message and creates the window button using InitButton() procedure
  • DefaultWindowProc handles button message
Ing. Gerardo Sánchez
  • 1,607
  • 15
  • 14