1
\$\begingroup\$

(See the previous question.)

Now I have incorporated some suggestions by Barmak Shemirani.

See what I have:

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

const int DEFAULT_WINDOW_WIDTH  = 300;
const int DEFAULT_WINDOW_HEIGHT = 200;
const int TEXT_LINE_HEIGHT = 30;

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
VOID WINAPI CopyToClipboard(HWND hwnd);

HWND hwnd;
COLORREF prevColor = 0xffffffff;
HHOOK mouseHook;

int WINAPI wWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PWSTR pCmdLine,
    int nCmdShow) {
    prevColor = RGB(0, 0, 0);
    const wchar_t CLASS_NAME[] = L"GrabPixlMainWndw";

    WNDCLASSEX wc = {};

    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClassEx(&wc);

    int screenWidth  = GetSystemMetrics(SM_CXFULLSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYFULLSCREEN);
        
    hwnd = CreateWindowEx(
        0,
        CLASS_NAME,
        L"GrabPixel 1.6 (by rodde)",
        WS_OVERLAPPEDWINDOW,
        (screenWidth  - DEFAULT_WINDOW_WIDTH) / 2,
        (screenHeight - DEFAULT_WINDOW_HEIGHT) / 2,
        DEFAULT_WINDOW_WIDTH,
        DEFAULT_WINDOW_HEIGHT,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (hwnd == NULL) {
        return 0;
    }

    mouseHook =
        SetWindowsHookExA(
            WH_MOUSE_LL,
            LowLevelMouseProc,
            hInstance,
            0);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);

    MSG msg = {};

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

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_KEYDOWN:
            if ((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000 && wParam == 'C') {
                CopyToClipboard(hwnd);
            }

            return 0;

        case WM_PAINT: 
        case WM_SHOWWINDOW:
        {
            if (uMsg == WM_SHOWWINDOW && !wParam) {
                return 0;
            }

            RECT rect;
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            if ((GetKeyState(VK_LBUTTON) & 0X80000) == 0) {
                // Here, the mouse left button is up.
                // Not resizing.
                POINT pt;
                GetCursorPos(&pt);
                HDC hdcDesktop = GetDC(NULL);
                COLORREF clr = GetPixel(hdcDesktop, pt.x, pt.y);
                ReleaseDC(NULL, hdcDesktop);
                prevColor = clr;

                // Make sure that the cursor is not the resize cursor
                // when changing the window size by dragging the 
                // window corner:
                HCURSOR cursor = LoadCursor(NULL, IDC_ARROW);
                SetCursor(cursor);
            }

            GetClientRect(hwnd, &rect);
            rect.bottom -= TEXT_LINE_HEIGHT;

            // Fill the actual client area:
            HBRUSH brush = CreateSolidBrush(prevColor);
            FillRect(hdc, &rect, brush);
            DeleteObject(brush);

            // Draw the RGB rectangle:
            brush = CreateSolidBrush(RGB(255, 255, 255));
            rect.top = rect.bottom;
            rect.bottom += TEXT_LINE_HEIGHT;
            FillRect(hdc, &rect, brush);
            DeleteObject(brush);

            // Print the RGB value of the current pixel:
            wchar_t buffer[10];

            wsprintf(buffer,
                L"#%02x%02x%02x",
                GetRValue(prevColor),
                GetGValue(prevColor),
                GetBValue(prevColor));

            SetTextColor(hdc, RGB(0, 0, 0));

            HFONT hFont = CreateFont(30,
                                     16,
                                     0,
                                     0,
                                     FW_BOLD,
                                     FALSE,
                                     FALSE,
                                     FALSE,
                                     ANSI_CHARSET,
                                     OUT_DEFAULT_PRECIS,
                                     CLIP_DEFAULT_PRECIS,
                                     ANTIALIASED_QUALITY,
                                     FF_MODERN,
                                     L"Monospaced");

            HFONT oldFont = (HFONT) SelectObject(hdc, hFont);

            TextOut(hdc,
                    5,
                    rect.bottom - TEXT_LINE_HEIGHT,
                    buffer,
                    7);

            SelectObject(hdc, oldFont);
            DeleteObject(hFont);

            EndPaint(hwnd, &ps);
            return 0;
        }

        case WM_SIZE:
            InvalidateRect(hwnd, NULL, FALSE);
            return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (wParam == WM_MOUSEMOVE) {
        BOOL isMouseDown = (GetKeyState(VK_LBUTTON) & 0x8000) != 0;

        if (!isMouseDown) {
            InvalidateRect(hwnd, NULL, FALSE);
            return 0;
        }
    }

    return CallNextHookEx(mouseHook, nCode, wParam, lParam);
}

VOID WINAPI CopyToClipboard(HWND hwnd) {
    HGLOBAL hglbCopy;
    LPSTR lpstrCopy;

    if (!OpenClipboard(GetDesktopWindow())) {
        return;
    }

    EmptyClipboard();

    POINT point;

    if (GetCursorPos(&point)) {
        int x = point.x;
        int y = point.y;
        COLORREF color = GetPixel(GetDC(NULL), x, y);
        int r = GetRValue(color);
        int g = GetGValue(color);
        int b = GetBValue(color);

        char buffer[10];
        sprintf_s(buffer, "#%02x%02x%02x", r, g, b);
        HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, 10);

        if (!hGlobal) {
            CloseClipboard();
            return;
        }

        memcpy(GlobalLock(hGlobal), buffer, 10);
        GlobalUnlock(hGlobal);
        SetClipboardData(CF_TEXT, hGlobal);
        CloseClipboard();
        GlobalFree(hGlobal);
    }
}

What's new

  1. The resize lag bug seems to be fixed.
  2. The app works on scaled desktops too.

Critique request

Is there anything to improve?

\$\endgroup\$
3
  • \$\begingroup\$ BTW, it seems difficult to resize the window via dragging a corner as if the border was only 1 pixel wide. \$\endgroup\$
    – coderodde
    Commented Feb 10, 2022 at 9:09
  • 1
    \$\begingroup\$ Ctrl+C will copy the color when application has focus. If app doesn't have focus, it won't receive keyboard input, it will just monitor the color. This can confuse the user. You could write it, such that it copies the color using mouse-down click, then stop monitoring if focus is lost. Or, add keyboard hook to keep copying the color (you would need a less common key combination) \$\endgroup\$ Commented Feb 10, 2022 at 13:21
  • \$\begingroup\$ @BarmakShemirani I am struggling with " You could write it, such that it copies the color using mouse-down click, then stop monitoring if focus is lost. Or, add keyboard hook to keep copying the color (you would need a less common key combination) " part. Could you consider compiling a full-fledge answer. I have a feeling that one might be a good one. \$\endgroup\$
    – coderodde
    Commented Feb 10, 2022 at 13:37

1 Answer 1

1
\$\begingroup\$

Syntax:

Avoid Windows old Hungarian notation. For example, write hfont instead of hFont.

Use void instead of VOID WINAPI for local functions. The WINAPI type qualifier is not necessary for local functions. VOID was needed in old c-compilers.

Functionality:

This program copies the color to clipboard when mouse is clicked. This behavior is not ideal, because if the program does not have focus, then each mouse click is modifying the clipboard without user's knowledge, even when the program is hidden. Moreover, if you click with the intention of copying the color, clipboard is quickly changed after next click, you loose that color.

Ctrl+C is also used to copy the color, but this works only when program has focus.

A better option is to add keyboard hook, and select (not copy) the desired color by pressing a key (for example 'S'). Then we copy the color by pressing Ctrl+C when program has focus. This way we don't interfere with keyboard and mouse input for other programs.

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

HWND main_wnd;
HHOOK mouse_hook;
HHOOK keyboard_hook;
int button_id = 100;
COLORREF color_sel = 0xffffffff; //color-selection
COLORREF color_cur = 0xffffffff; //color-cursor
COLORREF bkcolor;
HFONT hfont;

void CopyToClipboard(HWND hwnd)
{
    if (!OpenClipboard(hwnd))
        return;
    EmptyClipboard();

    int len = 10;
    HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, len);
    if (!hmem) return;
    char* buffer = GlobalLock(hmem);
    if (!buffer) return;
    sprintf_s(buffer, len, "#%02x%02x%02x",
        GetRValue(color_sel), GetGValue(color_sel), GetBValue(color_sel));
    GlobalUnlock(buffer);

    SetClipboardData(CF_TEXT, hmem);
    CloseClipboard();
}

//get button's rectangle relative to top-left corner of window
RECT get_button_rect(HWND hparent, HWND hbutton)
{
    POINT pt = { 0 };
    ClientToScreen(hparent, &pt);
    RECT rc;
    GetWindowRect(hbutton, &rc);
    OffsetRect(&rc, -pt.x, -pt.y);
    return rc;
}

void drawbox(HDC hdc, RECT rc, HFONT hfont, COLORREF c, const wchar_t* prefix)
{
    HBRUSH brush = CreateSolidBrush(c);
    HBRUSH oldbrush = SelectObject(hdc, brush);
    Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
    SelectObject(hdc, oldbrush);
    DeleteObject(brush);
    OffsetRect(&rc, rc.right - rc.left + 4, 0);
    rc.right += 200;

    wchar_t text[100];
    wsprintf(text, L"%s#%02x%02x%02x", 
            prefix, GetRValue(c), GetGValue(c), GetBValue(c));
    DrawText(hdc, text, -1, &rc, DT_LEFT | DT_VCENTER);
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case WM_CREATE:
    {
        HWND hbutton = CreateWindow(L"BUTTON", L"&Copy", WS_CHILD | WS_VISIBLE, 
            10, 10, 100, 40, hwnd, (HMENU)button_id, GetModuleHandle(NULL), NULL);
        hfont = CreateFont(20, 0, 0, 0, FW_BOLD, 0, 0, 0,
            0, 0, 0, ANTIALIASED_QUALITY, FF_MODERN, L"Consolas");
        SendMessage(hbutton, WM_SETFONT, (WPARAM)hfont, 0);
        return 0;
    }

    case WM_COMMAND:
        if (LOWORD(wparam) == button_id)
            CopyToClipboard(hwnd);
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        //prepare drawing
        HFONT oldfont = SelectObject(hdc, hfont);
        SetBkColor(hdc, bkcolor);
        
        RECT rc = get_button_rect(hwnd, GetDlgItem(hwnd, button_id));

        //draw color selection:
        OffsetRect(&rc, 0, rc.bottom - rc.top + 10);
        drawbox(hdc, rc, hfont, color_sel, L"Selected:\n");

        //draw color at the cursor:
        OffsetRect(&rc, 0, rc.bottom - rc.top + 10);
        drawbox(hdc, rc, hfont, color_cur, L"Press 'S' to select color:\n");

        //cleanup:
        SelectObject(hdc, oldfont);
        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_SIZE:
        InvalidateRect(hwnd, NULL, FALSE);
        return 0;

    case WM_DESTROY:
        DeleteObject(hfont);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

LRESULT CALLBACK mouse_proc(int nCode, WPARAM wparam, LPARAM lparam)
{
    if (wparam == WM_MOUSEMOVE && (GetKeyState(VK_LBUTTON) & 0X80000) == 0)
    {
        MSLLHOOKSTRUCT* kb = (MSLLHOOKSTRUCT*)lparam;
        HDC hdc_desktop = GetDC(0);
        COLORREF clr = GetPixel(hdc_desktop, kb->pt.x, kb->pt.y);
        ReleaseDC(0, hdc_desktop);
        if (color_cur != clr)
        {
            color_cur = clr;
            InvalidateRect(main_wnd, NULL, FALSE);
        }
    }
    return CallNextHookEx(mouse_hook, nCode, wparam, lparam);
}

LRESULT CALLBACK keyboard_proc(int code, WPARAM wparam, LPARAM lparam)
{
    if (code >= HC_ACTION && wparam == WM_KEYDOWN)
    {
        KBDLLHOOKSTRUCT* kb = (KBDLLHOOKSTRUCT*)lparam;
        if (kb->vkCode == 'S')
        {
            color_sel = color_cur;
            InvalidateRect(main_wnd, NULL, FALSE);
        }
    }
    return CallNextHookEx(keyboard_hook, code, wparam, lparam);
}

int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE prev, PWSTR cmd, int show)
{
    //Make sure process is DPI aware. DPI awareness should be set in manifest file
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    if (!IsProcessDPIAware())
        MessageBox(0, L"!IsProcessDPIAware", 0, MB_APPLMODAL);

    bkcolor = GetSysColor(COLOR_3DFACE);
    HBRUSH bkbrush = CreateSolidBrush(bkcolor);

    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(wc);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hinstance;
    wc.lpszClassName = L"GrabPixlMainWndw";
    wc.hbrBackground = bkbrush;
    RegisterClassEx(&wc);

    mouse_hook = SetWindowsHookEx(WH_MOUSE_LL, mouse_proc, hinstance, 0);
    keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_proc, hinstance, 0);

    //add acceslartor for Ctrl+C
    ACCEL accel[1]; 
    accel[0].fVirt = FCONTROL | FVIRTKEY;
    accel[0].key = 'C';
    accel[0].cmd = button_id;
    HACCEL haccel = CreateAcceleratorTable(accel, 1);

    const int w = 450;
    const int h = 230;
    main_wnd = CreateWindowEx(0, wc.lpszClassName, L"GrabPixel 1.6", 
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        (GetSystemMetrics(SM_CXFULLSCREEN)-w)/2,
        (GetSystemMetrics(SM_CYFULLSCREEN)-h)/2,
        w, h, NULL, NULL, hinstance, NULL);
    ShowWindow(main_wnd, show);
    UpdateWindow(main_wnd);

    MSG msg = { 0 };
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
        if (!TranslateAccelerator(main_wnd, haccel, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    DeleteObject(bkbrush);
    return 0;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.