////////////////////////////////////////////////////////////////////////
// QuickResNT.cpp

#define STRICT
#define WIN32_EXTRA_LEAN
#include <windows.h>
#include <stdio.h>

#include "resource.h"

HINSTANCE       hInst;
HWND            hWndMain;
WNDCLASSEX      wc;
NOTIFYICONDATA  nid;
char            szAppName[256] = "QuickResNT";
char            szText[256];
BOOL            g_bNT4;
HMENU           g_hMenu = NULL;

////////////////////////////////////////////////////////////////////////
// Implementation of a double-linked list to store DEVMODE structures.

class   CDevMode : public DEVMODE
{
public:
    CDevMode()
    {
        pNext = NULL;
        pPrev = NULL;
    }

    CDevMode *pNext;
    CDevMode *pPrev;

    UINT    m_nID;
};

class CDevModeList
{
public:
    CDevModeList()
    {
        pHead = pTail = NULL;
    }

    ~CDevModeList()
    {
        RemoveAll();
    }

    void RemoveAll()
    {
        while (pHead)
        {
            CDevMode *pNext = pHead->pNext;
            delete pHead;
            pHead = pNext;
        }
        pTail = NULL;
    }

    void AddHead(DEVMODE& m)
    {
        CDevMode *pDM = new CDevMode;

        *(DEVMODE *)pDM = m;
        pDM->pNext = pHead;
        pDM->pPrev = NULL;
        pHead = pDM;
        if (pDM->pNext)
            pDM->pNext->pPrev = pDM;
        if (!pTail)
            pTail = pHead;
    }
    
    void AddTail(DEVMODE& m)
    {
        CDevMode *pDM = new CDevMode;

        *(DEVMODE *)pDM = m;
        pDM->pPrev = pTail;
        pDM->pNext = NULL;
        pTail = pDM;
        if (pDM->pPrev)
            pDM->pPrev->pNext = pDM;
        if (!pHead)
            pHead = pTail;
    }

    void InsertBefore(CDevMode *p, DEVMODE& m)
    {
        CDevMode *pDM = new CDevMode;

        *(DEVMODE *)pDM = m;
        pDM->pPrev = p->pPrev;
        pDM->pNext = p;
        p->pPrev = pDM;
        if (pDM->pPrev)
            pDM->pPrev->pNext = pDM;
        if (pHead == p)
            pHead = pDM;
    }

    void InsertAfter(CDevMode *p, DEVMODE& m)
    {
        CDevMode *pDM = new CDevMode;

        *(DEVMODE *)pDM = m;
        pDM->pPrev = p;
        pDM->pNext = p->pNext;
        p->pNext = pDM;
        if (pDM->pNext)
            pDM->pNext->pPrev = pDM;
        if (pTail == p)
            pTail = pDM;
    }

    // Add a DEVMODE to the list.
    // Here is where the mode-selection logic is...
    //
    // m is the proposed mode.
    // bMultipleRes is TRUE if multiple refresh rates are allowed.
    void Add(DEVMODE& m, BOOL bMultipleRes)
    {
        if ((m.dmDisplayFrequency > 1) && (m.dmDisplayFrequency < 60))
            return// lower limit at 60Hz

        if (g_bNT4 && (m.dmDisplayFrequency > 85))
            return// upper limit at 85Hz

        CDevMode    *pDM;

        for (pDM = pHead; pDM; pDM = pDM->pNext)
        {
            if (pDM->dmBitsPerPel < m.dmBitsPerPel)
                continue;
            if (pDM->dmBitsPerPel > m.dmBitsPerPel)
                break;
            if (pDM->dmPelsWidth < m.dmPelsWidth)
                continue;
            if (pDM->dmPelsWidth > m.dmPelsWidth)
                break;
            if (pDM->dmPelsHeight < m.dmPelsHeight)
                continue;
            if (pDM->dmPelsHeight > m.dmPelsHeight)
                break;
            if (bMultipleRes)
            {
                if (pDM->dmDisplayFrequency < m.dmDisplayFrequency)
                    continue;
                if (pDM->dmDisplayFrequency > m.dmDisplayFrequency)
                    break;
            }
            else
            {
                if (pDM->dmDisplayFrequency < m.dmDisplayFrequency)
                {
                    // better fit
                    *(DEVMODE *)pDM = m;
                    return;
                }
                if (pDM->dmDisplayFrequency > m.dmDisplayFrequency)
                    return;     // dont want you - got a better fit
            }
            break;
        }
        if (pDM)
            InsertBefore(pDM, m);
        else
            AddTail(m);
    }
    
    CDevMode    *pHead;
    CDevMode    *pTail;
};

CDevModeList    listModes;
CDevMode        *pCurDisplaySettings = NULL;

// Create the tray icons.
void DoCreateTaskbarItems(HWND hWnd)
{
    nid.cbSize = sizeof(nid);
    nid.hWnd = hWnd;
    nid.uID = 1;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.uCallbackMessage = WM_USER;
    nid.hIcon = wc.hIconSm;
    strcpy(nid.szTip, szAppName);
    Shell_NotifyIcon(NIM_ADD, &nid);
}

// Build the context menu of resolutions.
HMENU DoCreateContextMenu(void)
{
    MENUITEMINFO    mii;
    CDevMode        *pDMItem;
    UINT            cx, cy;
    UINT            bpp, ref;
    HMENU           hMenu, hMenuTempl;
    BOOL            bBreak, bMultipleRes;
    int             i, nItem;
    HDC             hDC;
    DEVMODE         m;

    bMultipleRes = g_bNT4 || (::GetKeyState(VK_SHIFT) & 0x80000000);

    // Read the modes into the list
    listModes.RemoveAll();
    for (i = 0; EnumDisplaySettings("\\\\.\\Display1", i, &m); i++)
    {
#ifdef _DEBUG
        sprintf(szText, "Mode: %dx%d, %dbpp, %dHz\r\n", m.dmPelsWidth, m.dmPelsHeight,
            m.dmBitsPerPel, m.dmDisplayFrequency);
        OutputDebugString(szText);
#endif
        listModes.Add(m, bMultipleRes);
    }

    // Look up our current display mode
    hDC = CreateIC("DISPLAY", NULL, NULL, NULL);
    cx = GetDeviceCaps(hDC, HORZRES);
    cy = GetDeviceCaps(hDC, VERTRES);
    bpp = GetDeviceCaps(hDC, BITSPIXEL);
    ref = GetDeviceCaps(hDC, VREFRESH);
    DeleteDC(hDC);

    bBreak = FALSE;
    nItem = 0;

    // Create the menu
    hMenuTempl = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MAIN));
    hMenuTempl = GetSubMenu(hMenuTempl, 0);
    hMenu = CreatePopupMenu();

    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_DATA | MIIM_ID | MIIM_TYPE | MIIM_STATE;
    mii.wID = ID_RESOLUTION0;
    mii.hSubMenu = NULL;

    // Populate the menu.
    for (pDMItem = listModes.pHead; pDMItem; pDMItem = pDMItem->pNext)
    {
        if (pDMItem->pPrev && (pDMItem->pPrev->dmBitsPerPel != pDMItem->dmBitsPerPel))
        {
            if (bMultipleRes)
                bBreak = TRUE;
            else
                InsertMenu(hMenu, nItem++, MF_BYPOSITION | MF_SEPARATOR, -1, NULL);
        }
    
        if ((pDMItem->dmPelsWidth < 640) || (pDMItem->dmPelsHeight < 480))
            continue;

        if (pDMItem->dmDisplayFrequency <= 1)
        {
            sprintf(szText, "%d by %d, %d-bit color", 
                pDMItem->dmPelsWidth, pDMItem->dmPelsHeight, pDMItem->dmBitsPerPel);
        }
        else
        {
            sprintf(szText, "%d by %d, %d-bit color, %dHz", 
                pDMItem->dmPelsWidth, pDMItem->dmPelsHeight, pDMItem->dmBitsPerPel, 
                pDMItem->dmDisplayFrequency);
        }

        if ((pDMItem->dmPelsWidth == cx) &&
            (pDMItem->dmPelsHeight == cy) &&
            (pDMItem->dmBitsPerPel == bpp) &&
            (pDMItem->dmDisplayFrequency == ref))
        {   mii.fState = MFS_ENABLED | MFS_CHECKED;
            pCurDisplaySettings = pDMItem;
        }
        else
            mii.fState = MFS_ENABLED;
        
        mii.dwItemData = (DWORD)pDMItem;
        mii.dwTypeData = szText;
        mii.cch = strlen(szText);
        pDMItem->m_nID = mii.wID;
        if (bBreak)
        {
            bBreak = FALSE;
            mii.fType = MFT_STRING | MFT_MENUBARBREAK;
        }
        else
            mii.fType = MFT_STRING;
        InsertMenuItem(hMenu, nItem++, TRUE, &mii);
        mii.wID++;
    }

    // Add the template popup menu items.
    for (i = 1; i < GetMenuItemCount(hMenuTempl); i++)
    {
        mii.fMask = MIIM_DATA | MIIM_ID | MIIM_TYPE | MIIM_STATE;
        mii.dwTypeData = szText;
        mii.cch = sizeof(szText);
        GetMenuItemInfo(hMenuTempl, i, TRUE, &mii);
        InsertMenuItem(hMenu, nItem++, TRUE, &mii);
    }
    SetMenuDefaultItem(hMenu, ID_PROPERTIES, FALSE);
    return hMenu;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    POINT           p;
    CDevMode        *pDMItem;
static  UINT        s_uTaskbarRestart;
    MENUITEMINFO    mii;

    switch (uMsg)
    {
    case WM_CREATE:
        DoCreateTaskbarItems(hWnd);
        // Register the message that will tell us if Explorer restarts.
        s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
        g_hMenu = NULL;
        return 0L;

    case WM_USER:
        // Message for tray icon notification.
        GetCursorPos(&p);
        switch (lParam)
        {
//      case WM_LBUTTONDOWN:
        case WM_RBUTTONDOWN:
            SetForegroundWindow(hWnd);
            if (g_hMenu)
                DestroyMenu(g_hMenu);
            g_hMenu = DoCreateContextMenu();
            TrackPopupMenu(g_hMenu, TPM_LEFTBUTTON, p.x, p.y, 0, hWnd, NULL);
            break;

        case WM_LBUTTONDBLCLK:
            SendMessage(hWnd, WM_COMMAND, ID_PROPERTIES, 0L);
            break;
        }
        return 0L;

    case WM_COMMAND:
        switch (wParam)
        {
        case ID_PROPERTIES:
            // Run the Control Panel Desktop applet
            // This is the lazy way:
            ShellExecute(hWnd, NULL, "rundll32.exe", "shell32.dll,Control_RunDLL desk.cpl",
                "%SystemRoot%\\System32", SW_SHOW);
            break;

        case ID_EXIT:
            DestroyWindow(hWnd);
            break;

        default:
            // Look up the mode in the list from the menu item
            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_DATA | MIIM_STATE;
            
            if (!GetMenuItemInfo(g_hMenu, wParam, FALSE, &mii))
                break;

            pDMItem = (CDevMode *)mii.dwItemData;
            if (ChangeDisplaySettings(pDMItem, CDS_TEST) == DISP_CHANGE_SUCCESSFUL)
            {
                ChangeDisplaySettings(pDMItem, CDS_UPDATEREGISTRY);
                pCurDisplaySettings = pDMItem;
            }
            else
                MessageBox(hWnd, "Mode change failed!", szAppName, MB_OK);
            break;
        }
        return 0L;

    case WM_DESTROY:
        Shell_NotifyIcon(NIM_DELETE, &nid);
        PostQuitMessage(0);
        return 0L;

    default:
        // If Explorer has restarted, rebuild our tray icons
        if (uMsg == s_uTaskbarRestart)
            DoCreateTaskbarItems(hWnd);
        break;
    }

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
    MSG             msg;
    OSVERSIONINFO   osvi;

    hInst = hInstance;

    LoadString(hInst, IDS_APPNAME, szAppName, sizeof(szAppName));

    // Check for Windows NT and its version.
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx(&osvi);

    if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) || (osvi.dwMajorVersion < 4))
    {
        MessageBox(NULL, "This application requires Windows NT version 4 or above.", szAppName, MB_OK);
        return 2;
    }

    // We need to test for NT 4, since it doesnt have support for monitor drivers;
    //  as a result, its impossible to check refresh rates...
    g_bNT4 = (osvi.dwMajorVersion == 4) ? TRUE : FALSE;

    wc.cbSize = sizeof(wc);
    wc.style = 0;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_MAIN));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szAppName;
    wc.hIconSm = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON,
        GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 
        LR_DEFAULTCOLOR);
    RegisterClassEx(&wc);

    hWndMain = CreateWindowEx(0, szAppName, szAppName, WS_POPUP, 0, 0, 1, 1, 
        NULL, NULL, hInst, NULL);
    if (!hWndMain)
        return 1;

    while (GetMessage(&msg, NULL, 0, 0) > 0)
        DispatchMessage(&msg);

    return msg.wParam;
}