#include "pch.h"
#include "framework.h"
#include "HwsCaptureMainDlg.h"
#include "afxdialogex.h"
#include <vector>
#include <string>
#include <windowsx.h>
#include <algorithm>
#include <commdlg.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

static CString FormatCapabilityText(const HWS_VIDEO_CAP& cap)
{
    int fps = cap.fpsDen ? (cap.fpsNum / cap.fpsDen) : 0;
    CString s;
    s.Format(L"%dx%d %dfps %s", cap.width, cap.height, fps, HwsPixelFormatText(cap.pixelFormat));
    return s;
}

static bool IsDemoRenderablePixelFormat(HWS_PIXEL_FORMAT fmt)
{
    switch (fmt) {
    case HWS_PIXFMT_YUY2:
    case HWS_PIXFMT_NV12:
    case HWS_PIXFMT_RGB24:
    case HWS_PIXFMT_RGB32:
        return true;
    case HWS_PIXFMT_MJPG:
    default:
        return false;
    }
}

class CVideoSourceDlg : public CDialogEx
{
public:
    HWS_VIDEO_INPUT_CONFIG config = {};
    std::vector<HWS_VIDEO_CAP> caps;

    explicit CVideoSourceDlg(CWnd* p) : CDialogEx(IDD_VIDEO_SOURCE, p) {}

    ~CVideoSourceDlg() override
    {
        ClearLocalCache();
    }

    void ClearLocalCache()
    {
        caps.clear();
        caps.shrink_to_fit();
    }

    HWND DeviceCombo() const { return ::GetDlgItem(m_hWnd, IDC_DEVICE_COMBO); }
    HWND CapCombo() const { return ::GetDlgItem(m_hWnd, IDC_CAP_COMBO); }

    void ResetCombo(HWND combo)
    {
        if (combo) ::SendMessageW(combo, CB_RESETCONTENT, 0, 0);
    }

    void AddComboString(HWND combo, const CString& text)
    {
        if (combo) ::SendMessageW(combo, CB_ADDSTRING, 0, (LPARAM)(LPCWSTR)text);
    }

    int GetComboSel(HWND combo) const
    {
        if (!combo) return -1;
        return (int)::SendMessageW(combo, CB_GETCURSEL, 0, 0);
    }

    void SetComboSel(HWND combo, int index)
    {
        if (combo) ::SendMessageW(combo, CB_SETCURSEL, index, 0);
    }

    int GetComboCount(HWND combo) const
    {
        if (!combo) return 0;
        return (int)::SendMessageW(combo, CB_GETCOUNT, 0, 0);
    }

    BOOL OnInitDialog() override
    {
        CDialogEx::OnInitDialog();

        HWND dev = DeviceCombo();
        HWND cap = CapCombo();
        ResetCombo(dev);
        ResetCombo(cap);

        const int n = HWS_GetVideoDeviceCount();
        for (int i = 0; i < n; ++i) {
            HWS_DEVICE_INFO info = {};
            if (HWS_GetVideoDeviceInfo(i, &info) == HWS_OK) {
                CString name(HwsUtf8ToWide(info.name).c_str());
                CString item;
                item.Format(L"%d: %s", i, name.GetString());
                AddComboString(dev, item);
            }
        }

        if (n > 0) SetComboSel(dev, 0);
        LoadCaps();
        return TRUE;
    }

    void LoadCaps()
    {
        HWND dev = DeviceCombo();
        HWND capBox = CapCombo();
        ClearLocalCache();
        ResetCombo(capBox);

        const int d = GetComboSel(dev);
        if (d < 0) return;

        const int count = HWS_GetVideoCapabilityCount(d);
        caps.reserve((size_t)count);
        for (int i = 0; i < count; ++i) {
            HWS_VIDEO_CAP c = {};
            if (HWS_GetVideoCapability(d, i, &c) != HWS_OK) continue;
            if (c.width <= 0 || c.height <= 0 || c.pixelFormat == HWS_PIXFMT_UNKNOWN) continue;
            if (!IsDemoRenderablePixelFormat(c.pixelFormat)) continue;
            caps.push_back(c);
            AddComboString(capBox, FormatCapabilityText(c));
        }

        if (GetComboCount(capBox) > 0) SetComboSel(capBox, 0);
    }

    void OnOK() override
    {
        HWND dev = DeviceCombo();
        HWND capBox = CapCombo();
        const int d = GetComboSel(dev);
        const int c = GetComboSel(capBox);

        if (d >= 0 && c >= 0 && c < (int)caps.size()) {
            HWS_VIDEO_CAP cap = caps[(size_t)c];
            config.deviceIndex = d;
            config.width = cap.width;
            config.height = cap.height;
            config.fps = cap.fpsDen ? cap.fpsNum / cap.fpsDen : 30;
            config.pixelFormat = cap.pixelFormat;
            config.colorSpace = HWS_COLOR_SPACE_AUTO;
            config.colorRange = HWS_COLOR_RANGE_AUTO;
            config.bufferCount = 4;
            CDialogEx::OnOK();
        }
    }

    afx_msg void OnSelChangeDevice()
    {
        LoadCaps();
    }

    afx_msg void OnDestroy()
    {
        ResetCombo(CapCombo());
        ResetCombo(DeviceCombo());
        ClearLocalCache();
        CDialogEx::OnDestroy();
    }

    DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CVideoSourceDlg, CDialogEx)
    ON_CBN_SELCHANGE(IDC_DEVICE_COMBO, &CVideoSourceDlg::OnSelChangeDevice)
    ON_WM_DESTROY()
END_MESSAGE_MAP()

class CAudioSourceDlg : public CDialogEx
{
public:
    int deviceIndex = 0;
    explicit CAudioSourceDlg(CWnd* p) : CDialogEx(IDD_AUDIO_SOURCE, p) {}

    HWND DeviceCombo() const { return ::GetDlgItem(m_hWnd, IDC_DEVICE_COMBO); }

    BOOL OnInitDialog() override
    {
        CDialogEx::OnInitDialog();
        HWND dev = DeviceCombo();
        if (dev) ::SendMessageW(dev, CB_RESETCONTENT, 0, 0);

        const int n = HWS_GetAudioInputDeviceCount();
        for (int i = 0; i < n; ++i) {
            HWS_DEVICE_INFO info = {};
            if (HWS_GetAudioInputDeviceInfo(i, &info) == HWS_OK) {
                CString item;
                item.Format(L"%d: %s", i, HwsUtf8ToWide(info.name).c_str());
                if (dev) ::SendMessageW(dev, CB_ADDSTRING, 0, (LPARAM)(LPCWSTR)item);
            }
        }
        if (n > 0 && dev) ::SendMessageW(dev, CB_SETCURSEL, 0, 0);
        return TRUE;
    }

    void OnOK() override
    {
        HWND dev = DeviceCombo();
        const int d = dev ? (int)::SendMessageW(dev, CB_GETCURSEL, 0, 0) : -1;
        if (d >= 0) {
            deviceIndex = d;
            CDialogEx::OnOK();
        }
    }

    afx_msg void OnDestroy()
    {
        HWND dev = DeviceCombo();
        if (dev) ::SendMessageW(dev, CB_RESETCONTENT, 0, 0);
        CDialogEx::OnDestroy();
    }

    DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CAudioSourceDlg, CDialogEx)
    ON_WM_DESTROY()
END_MESSAGE_MAP()


// Win32 dialog helpers copied/aligned with hwscapture_test.
#define IDC_RES_COMBO                 2002
#define IDC_FPS_COMBO                 2003
#define IDC_FMT_COMBO                 2004
#define IDC_CFG_VIDEO                 2005
#define IDC_CFG_CROSSBAR              2006
#define IDC_INFO_TEXT                 2007
#define IDC_OK_BUTTON                 2008
#define IDC_CANCEL_BUTTON             2009
#define IDC_OSD_TEXT_EDIT             3001
#define IDC_OSD_X_EDIT                3002
#define IDC_OSD_Y_EDIT                3003
#define IDC_OSD_W_EDIT                3004
#define IDC_OSD_H_EDIT                3005
#define IDC_OSD_OPACITY_EDIT          3006
#define IDC_OSD_FONT_EDIT             3007
#define IDC_OSD_TEXT_COLOR            3008
#define IDC_OSD_BG_COLOR              3009
#define IDC_OSD_TRANSPARENT           3010
#define IDC_OSD_FILE_EDIT             3011
#define IDC_OSD_BROWSE_FILE           3012
#define IDC_OSD_FONT_NAME             3013

struct HwsMfcCapItem { int width; int height; int fps; HWS_PIXEL_FORMAT fmt; };
struct HwsMfcVideoDialogContext { HWS_VIDEO_INPUT_CONFIG config; bool accepted; bool forceDeviceDefault; std::vector<HwsMfcCapItem> caps; };
struct HwsMfcOsdDialogContext { HWS_OSD_ITEM item; bool accepted; bool imageMode; };

static HWS_VIDEO_INPUT_CONFIG HwsMfcDefaultVideoConfig()
{
    HWS_VIDEO_INPUT_CONFIG cfg = {};
    cfg.deviceIndex = 0;
    cfg.width = 1280;
    cfg.height = 720;
    cfg.fps = 30;
    cfg.pixelFormat = HWS_PIXFMT_RGB32;
    cfg.colorSpace = HWS_COLOR_SPACE_AUTO;
    cfg.colorRange = HWS_COLOR_RANGE_AUTO;
    cfg.bufferCount = 4;
    return cfg;
}

static const char* HwsMfcPixelFormatName(HWS_PIXEL_FORMAT fmt)
{
    switch (fmt) {
    case HWS_PIXFMT_YUY2: return "YUY2";
    case HWS_PIXFMT_NV12: return "NV12";
    case HWS_PIXFMT_RGB24: return "RGB24";
    case HWS_PIXFMT_RGB32: return "RGB32";
    case HWS_PIXFMT_MJPG: return "MJPG";
    default: return "UNKNOWN";
    }
}

static void HwsMfcCenterWindowToParent(HWND hwnd, HWND parent)
{
    RECT rc = {}, prc = {};
    GetWindowRect(hwnd, &rc);
    if (parent) GetWindowRect(parent, &prc); else SystemParametersInfoA(SPI_GETWORKAREA, 0, &prc, 0);
    int w = rc.right - rc.left, h = rc.bottom - rc.top;
    int x = prc.left + ((prc.right - prc.left) - w) / 2;
    int y = prc.top + ((prc.bottom - prc.top) - h) / 2;
    SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}

static void HwsMfcAddComboItem(HWND combo, const char* text, LPARAM data)
{
    int pos = (int)SendMessageA(combo, CB_ADDSTRING, 0, (LPARAM)text);
    SendMessageA(combo, CB_SETITEMDATA, pos, data);
}

static int HwsMfcGetComboData(HWND hwnd, int id)
{
    HWND combo = GetDlgItem(hwnd, id);
    int sel = (int)SendMessageA(combo, CB_GETCURSEL, 0, 0);
    if (sel == CB_ERR) return -1;
    return (int)SendMessageA(combo, CB_GETITEMDATA, sel, 0);
}

static int HwsMfcResolutionKey(int w, int h) { return (w << 16) ^ h; }
static int HwsMfcFmtKey(HWS_PIXEL_FORMAT fmt) { return (int)fmt; }

static void HwsMfcLoadCaps(HwsMfcVideoDialogContext* ctx, int deviceIndex)
{
    ctx->caps.clear();
    int count = HWS_GetVideoCapabilityCount(deviceIndex);
    for (int i = 0; i < count; ++i) {
        HWS_VIDEO_CAP cap = {};
        if (HWS_GetVideoCapability(deviceIndex, i, &cap) == HWS_OK) {
            if (!IsDemoRenderablePixelFormat(cap.pixelFormat)) {
                continue;
            }
            HwsMfcCapItem item = {cap.width, cap.height, cap.fpsNum > 0 ? cap.fpsNum : 30, cap.pixelFormat};
            bool exists = false;
            for (size_t k = 0; k < ctx->caps.size(); ++k) {
                if (ctx->caps[k].width == item.width && ctx->caps[k].height == item.height && ctx->caps[k].fps == item.fps && ctx->caps[k].fmt == item.fmt) { exists = true; break; }
            }
            if (!exists) ctx->caps.push_back(item);
        }
    }
    if (ctx->caps.empty()) ctx->caps.push_back({1280, 720, 30, HWS_PIXFMT_RGB32});
}

static void HwsMfcFillDeviceCombo(HWND hwnd, int currentIndex)
{
    HWND combo = GetDlgItem(hwnd, IDC_DEVICE_COMBO);
    SendMessageA(combo, CB_RESETCONTENT, 0, 0);
    int count = HWS_GetVideoDeviceCount();
    int selectPos = 0;
    for (int i = 0; i < count; ++i) {
        HWS_DEVICE_INFO info = {};
        if (HWS_GetVideoDeviceInfo(i, &info) == HWS_OK) {
            char line[320]; sprintf_s(line, "%d: %s", i, info.name);
            int pos = (int)SendMessageA(combo, CB_ADDSTRING, 0, (LPARAM)line);
            SendMessageA(combo, CB_SETITEMDATA, pos, (LPARAM)i);
            if (i == currentIndex) selectPos = pos;
        }
    }
    if (count > 0) SendMessageA(combo, CB_SETCURSEL, selectPos, 0);
}

static bool HwsMfcFindExactCap(const HwsMfcVideoDialogContext* ctx, int w, int h, int fps, HWS_PIXEL_FORMAT fmt)
{
    if (!ctx) return false;
    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        const HwsMfcCapItem& c = ctx->caps[i];
        if (c.width == w && c.height == h && c.fps == fps && c.fmt == fmt) return true;
    }
    return false;
}

static void HwsMfcApplyFirstCapToConfig(HwsMfcVideoDialogContext* ctx)
{
    if (!ctx || ctx->caps.empty()) return;
    const HwsMfcCapItem& c = ctx->caps[0];
    ctx->config.width = c.width;
    ctx->config.height = c.height;
    ctx->config.fps = c.fps;
    ctx->config.pixelFormat = c.fmt;
}

static void HwsMfcNormalizeVideoConfigToCaps(HwsMfcVideoDialogContext* ctx)
{
    if (!ctx || ctx->caps.empty()) return;

    if (ctx->forceDeviceDefault ||
        ctx->config.width <= 0 ||
        ctx->config.height <= 0 ||
        ctx->config.fps <= 0 ||
        ctx->config.pixelFormat == (HWS_PIXEL_FORMAT)0) {
        HwsMfcApplyFirstCapToConfig(ctx);
        ctx->forceDeviceDefault = false;
        return;
    }

    if (HwsMfcFindExactCap(ctx, ctx->config.width, ctx->config.height, ctx->config.fps, ctx->config.pixelFormat)) {
        return;
    }

    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        const HwsMfcCapItem& c = ctx->caps[i];
        if (c.width == ctx->config.width && c.height == ctx->config.height && c.fps == ctx->config.fps) {
            ctx->config.pixelFormat = c.fmt;
            return;
        }
    }

    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        const HwsMfcCapItem& c = ctx->caps[i];
        if (c.width == ctx->config.width && c.height == ctx->config.height) {
            ctx->config.fps = c.fps;
            ctx->config.pixelFormat = c.fmt;
            return;
        }
    }

    HwsMfcApplyFirstCapToConfig(ctx);
}

static void HwsMfcSyncSelectedVideoConfigFromControls(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    if (!ctx) return;

    int dev = HwsMfcGetComboData(hwnd, IDC_DEVICE_COMBO);
    int resKey = HwsMfcGetComboData(hwnd, IDC_RES_COMBO);
    int fps = HwsMfcGetComboData(hwnd, IDC_FPS_COMBO);
    int fmt = HwsMfcGetComboData(hwnd, IDC_FMT_COMBO);

    if (dev >= 0) ctx->config.deviceIndex = dev;
    if (resKey >= 0) {
        ctx->config.width = (resKey >> 16) & 0xffff;
        ctx->config.height = resKey & 0xffff;
    }
    if (fps > 0) ctx->config.fps = fps;
    if (fmt > 0) ctx->config.pixelFormat = (HWS_PIXEL_FORMAT)fmt;
}

static void HwsMfcFillResolutionCombo(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    HWND combo = GetDlgItem(hwnd, IDC_RES_COMBO);
    SendMessageA(combo, CB_RESETCONTENT, 0, 0);
    std::vector<int> keys;
    int selectPos = 0;
    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        int key = HwsMfcResolutionKey(ctx->caps[i].width, ctx->caps[i].height);
        if (std::find(keys.begin(), keys.end(), key) != keys.end()) continue;
        keys.push_back(key);
        char line[64]; sprintf_s(line, "%d x %d", ctx->caps[i].width, ctx->caps[i].height);
        int pos = (int)SendMessageA(combo, CB_ADDSTRING, 0, (LPARAM)line);
        SendMessageA(combo, CB_SETITEMDATA, pos, (LPARAM)key);
        if (ctx->caps[i].width == ctx->config.width && ctx->caps[i].height == ctx->config.height) selectPos = pos;
    }
    if (!keys.empty()) SendMessageA(combo, CB_SETCURSEL, selectPos, 0);
    HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
}

static void HwsMfcFillFpsCombo(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    HWND combo = GetDlgItem(hwnd, IDC_FPS_COMBO);
    SendMessageA(combo, CB_RESETCONTENT, 0, 0);
    int resKey = HwsMfcGetComboData(hwnd, IDC_RES_COMBO);
    std::vector<int> fpsList;
    int selectPos = 0;
    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        if (HwsMfcResolutionKey(ctx->caps[i].width, ctx->caps[i].height) != resKey) continue;
        if (std::find(fpsList.begin(), fpsList.end(), ctx->caps[i].fps) != fpsList.end()) continue;
        fpsList.push_back(ctx->caps[i].fps);
        char line[32]; sprintf_s(line, "%d", ctx->caps[i].fps);
        int pos = (int)SendMessageA(combo, CB_ADDSTRING, 0, (LPARAM)line);
        SendMessageA(combo, CB_SETITEMDATA, pos, (LPARAM)ctx->caps[i].fps);
        if (ctx->caps[i].fps == ctx->config.fps) selectPos = pos;
    }
    if (fpsList.empty()) HwsMfcAddComboItem(combo, "30", 30);
    SendMessageA(combo, CB_SETCURSEL, selectPos, 0);
    HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
}

static void HwsMfcFillFormatCombo(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    HWND combo = GetDlgItem(hwnd, IDC_FMT_COMBO);
    SendMessageA(combo, CB_RESETCONTENT, 0, 0);
    int resKey = HwsMfcGetComboData(hwnd, IDC_RES_COMBO);
    int fps = HwsMfcGetComboData(hwnd, IDC_FPS_COMBO);
    std::vector<int> fmtList;
    int selectPos = 0;
    for (size_t i = 0; i < ctx->caps.size(); ++i) {
        if (HwsMfcResolutionKey(ctx->caps[i].width, ctx->caps[i].height) != resKey || ctx->caps[i].fps != fps) continue;
        int fmt = HwsMfcFmtKey(ctx->caps[i].fmt);
        if (std::find(fmtList.begin(), fmtList.end(), fmt) != fmtList.end()) continue;
        fmtList.push_back(fmt);
        int pos = (int)SendMessageA(combo, CB_ADDSTRING, 0, (LPARAM)HwsMfcPixelFormatName(ctx->caps[i].fmt));
        SendMessageA(combo, CB_SETITEMDATA, pos, (LPARAM)fmt);
        if (ctx->caps[i].fmt == ctx->config.pixelFormat) selectPos = pos;
    }
    if (fmtList.empty()) HwsMfcAddComboItem(combo, "RGB32", HWS_PIXFMT_RGB32);
    SendMessageA(combo, CB_SETCURSEL, selectPos, 0);
    HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
}

static void HwsMfcUpdateInfoText(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
    int resKey = HwsMfcGetComboData(hwnd, IDC_RES_COMBO);
    int fps = HwsMfcGetComboData(hwnd, IDC_FPS_COMBO);
    int fmt = HwsMfcGetComboData(hwnd, IDC_FMT_COMBO);
    char text[512];
    sprintf_s(text, "Current Resolution : %d x %d\r\nCurrent FPS        : %d\r\nCurrent Format     : %s\r\nCapabilities       : %d",
              (resKey >> 16) & 0xffff, resKey & 0xffff, fps, HwsMfcPixelFormatName((HWS_PIXEL_FORMAT)fmt), (int)ctx->caps.size());
    SetWindowTextA(GetDlgItem(hwnd, IDC_INFO_TEXT), text);
}

static void HwsMfcReloadFormatControls(HWND hwnd, HwsMfcVideoDialogContext* ctx)
{
    HwsMfcNormalizeVideoConfigToCaps(ctx);
    HwsMfcFillResolutionCombo(hwnd, ctx);
    HwsMfcFillFpsCombo(hwnd, ctx);
    HwsMfcFillFormatCombo(hwnd, ctx);
    HwsMfcUpdateInfoText(hwnd, ctx);
}

static LRESULT CALLBACK HwsMfcVideoDeviceDialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    HwsMfcVideoDialogContext* ctx = (HwsMfcVideoDialogContext*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    switch (msg) {
    case WM_CREATE:
    {
        CREATESTRUCTA* cs = (CREATESTRUCTA*)lp;
        ctx = (HwsMfcVideoDialogContext*)cs->lpCreateParams;
        SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)ctx);
        CreateWindowA("STATIC", "Video Capture Device", WS_CHILD | WS_VISIBLE, 18, 20, 170, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 185, 18, 430, 180, hwnd, (HMENU)IDC_DEVICE_COMBO, NULL, NULL);
        CreateWindowA("BUTTON", "Configure Video", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 185, 55, 145, 28, hwnd, (HMENU)IDC_CFG_VIDEO, NULL, NULL);
        CreateWindowA("BUTTON", "Configure Crossbar", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 345, 55, 160, 28, hwnd, (HMENU)IDC_CFG_CROSSBAR, NULL, NULL);
        CreateWindowA("STATIC", "Resolution", WS_CHILD | WS_VISIBLE, 18, 105, 150, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 185, 102, 190, 180, hwnd, (HMENU)IDC_RES_COMBO, NULL, NULL);
        CreateWindowA("STATIC", "FPS", WS_CHILD | WS_VISIBLE, 400, 105, 50, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 450, 102, 165, 180, hwnd, (HMENU)IDC_FPS_COMBO, NULL, NULL);
        CreateWindowA("STATIC", "Pixel Format", WS_CHILD | WS_VISIBLE, 18, 145, 150, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 185, 142, 190, 180, hwnd, (HMENU)IDC_FMT_COMBO, NULL, NULL);
        CreateWindowA("STATIC", "Information", WS_CHILD | WS_VISIBLE, 18, 190, 150, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("STATIC", "", WS_CHILD | WS_VISIBLE | SS_LEFT, 185, 188, 430, 95, hwnd, (HMENU)IDC_INFO_TEXT, NULL, NULL);
        CreateWindowA("BUTTON", "OK", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 445, 325, 90, 30, hwnd, (HMENU)IDC_OK_BUTTON, NULL, NULL);
        CreateWindowA("BUTTON", "Cancel", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 555, 325, 90, 30, hwnd, (HMENU)IDC_CANCEL_BUTTON, NULL, NULL);
        HwsMfcFillDeviceCombo(hwnd, ctx->config.deviceIndex);
        HwsMfcLoadCaps(ctx, ctx->config.deviceIndex);
        HwsMfcReloadFormatControls(hwnd, ctx);
        HwsMfcCenterWindowToParent(hwnd, GetParent(hwnd));
        return 0;
    }
    case WM_COMMAND:
        switch (LOWORD(wp)) {
        case IDC_DEVICE_COMBO:
            if (HIWORD(wp) == CBN_SELCHANGE && ctx) {
                int dev = HwsMfcGetComboData(hwnd, IDC_DEVICE_COMBO);
                ctx->config.deviceIndex = dev;
                ctx->forceDeviceDefault = true;
                HwsMfcLoadCaps(ctx, dev);
                HwsMfcReloadFormatControls(hwnd, ctx);
            }
            return 0;
        case IDC_RES_COMBO:
            if (HIWORD(wp) == CBN_SELCHANGE && ctx) {
                HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
                HwsMfcFillFpsCombo(hwnd, ctx);
                HwsMfcFillFormatCombo(hwnd, ctx);
                HwsMfcUpdateInfoText(hwnd, ctx);
            }
            return 0;
        case IDC_FPS_COMBO:
            if (HIWORD(wp) == CBN_SELCHANGE && ctx) {
                HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
                HwsMfcFillFormatCombo(hwnd, ctx);
                HwsMfcUpdateInfoText(hwnd, ctx);
            }
            return 0;
        case IDC_FMT_COMBO:
            if (HIWORD(wp) == CBN_SELCHANGE && ctx) {
                HwsMfcSyncSelectedVideoConfigFromControls(hwnd, ctx);
                HwsMfcUpdateInfoText(hwnd, ctx);
            }
            return 0;
        case IDC_CFG_VIDEO:
            if (ctx) HWS_ShowVideoDevicePropertyPage(hwnd, HwsMfcGetComboData(hwnd, IDC_DEVICE_COMBO));
            return 0;
        case IDC_CFG_CROSSBAR:
            if (ctx) {
                HWS_RESULT r = HWS_ShowVideoCrossbarPropertyPage(hwnd, HwsMfcGetComboData(hwnd, IDC_DEVICE_COMBO));
                if (r != HWS_OK) MessageBoxA(hwnd, "This device does not expose a DirectShow crossbar property page.", "hwscapture", MB_ICONINFORMATION | MB_OK);
            }
            return 0;
        case IDC_OK_BUTTON:
        case IDOK:
            if (ctx) {
                int dev = HwsMfcGetComboData(hwnd, IDC_DEVICE_COMBO);
                int resKey = HwsMfcGetComboData(hwnd, IDC_RES_COMBO);
                int fps = HwsMfcGetComboData(hwnd, IDC_FPS_COMBO);
                int fmt = HwsMfcGetComboData(hwnd, IDC_FMT_COMBO);
                if (dev < 0 || resKey < 0 || fps <= 0 || fmt <= 0) {
                    MessageBoxA(hwnd, "Invalid video capture configuration.", "hwscapture", MB_ICONWARNING | MB_OK);
                    return 0;
                }
                ctx->config.deviceIndex = dev;
                ctx->config.width = (resKey >> 16) & 0xffff;
                ctx->config.height = resKey & 0xffff;
                ctx->config.fps = fps;
                ctx->config.pixelFormat = (HWS_PIXEL_FORMAT)fmt;
                ctx->config.colorSpace = HWS_COLOR_SPACE_AUTO;
                ctx->config.colorRange = HWS_COLOR_RANGE_AUTO;
                ctx->config.bufferCount = 4;
                ctx->accepted = true;
            }
            DestroyWindow(hwnd);
            return 0;
        case IDC_CANCEL_BUTTON:
        case IDCANCEL:
            DestroyWindow(hwnd);
            return 0;
        }
        break;
    case WM_CLOSE:
        DestroyWindow(hwnd);
        return 0;
    }
    return DefWindowProcA(hwnd, msg, wp, lp);
}

static bool HwsMfcShowVideoCaptureDeviceDialog(HWND parent, HWS_VIDEO_INPUT_CONFIG* config)
{
    HWS_RefreshDeviceList();
    HINSTANCE hInst = (HINSTANCE)GetModuleHandleA(NULL);
    WNDCLASSA wc = {};
    wc.lpfnWndProc = HwsMfcVideoDeviceDialogProc;
    wc.hInstance = hInst;
    wc.lpszClassName = "HwsMfcVideoDeviceDialogV331";
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    RegisterClassA(&wc);
    HwsMfcVideoDialogContext ctx = {};
    ctx.config = config ? *config : HwsMfcDefaultVideoConfig();
    ctx.forceDeviceDefault =
        (ctx.config.width <= 0 ||
         ctx.config.height <= 0 ||
         ctx.config.fps <= 0 ||
         ctx.config.pixelFormat == (HWS_PIXEL_FORMAT)0);
    HWND dlg = CreateWindowExA(WS_EX_DLGMODALFRAME, wc.lpszClassName, "Properties: Video Capture Device",
        WS_CAPTION | WS_SYSMENU | WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, 700, 440,
        parent, NULL, hInst, &ctx);
    if (!dlg) return false;
    EnableWindow(parent, FALSE);
    ShowWindow(dlg, SW_SHOW);
    UpdateWindow(dlg);
    MSG msg;
    while (IsWindow(dlg) && GetMessageA(&msg, NULL, 0, 0)) {
        if (!IsDialogMessageA(dlg, &msg)) { TranslateMessage(&msg); DispatchMessageA(&msg); }
    }
    EnableWindow(parent, TRUE);
    SetForegroundWindow(parent);
    if (ctx.accepted && config) { *config = ctx.config; return true; }
    return false;
}

static int HwsMfcGetDlgIntValue(HWND hwnd, int id, int defValue)
{
    char buf[64] = {};
    GetWindowTextA(GetDlgItem(hwnd, id), buf, sizeof(buf));
    if (!buf[0]) return defValue;
    return atoi(buf);
}

static float HwsMfcGetDlgFloatValue(HWND hwnd, int id, float defValue)
{
    char buf[64] = {};
    GetWindowTextA(GetDlgItem(hwnd, id), buf, sizeof(buf));
    if (!buf[0]) return defValue;
    return (float)atof(buf);
}

static void HwsMfcSetDlgIntValue(HWND hwnd, int id, int value)
{
    char buf[64]; sprintf_s(buf, "%d", value); SetWindowTextA(GetDlgItem(hwnd, id), buf);
}

static void HwsMfcSetDlgFloatValue(HWND hwnd, int id, float value)
{
    char buf[64]; sprintf_s(buf, "%.2f", value); SetWindowTextA(GetDlgItem(hwnd, id), buf);
}

static bool HwsMfcChooseRgbColor(HWND hwnd, unsigned int* argbColor)
{
    if (!argbColor) return false;
    CHOOSECOLOR cc = {};
    COLORREF custom[16] = {};
    cc.lStructSize = sizeof(cc);
    cc.hwndOwner = hwnd;
    cc.lpCustColors = custom;
    cc.Flags = CC_FULLOPEN | CC_RGBINIT;
    cc.rgbResult = RGB((*argbColor >> 16) & 0xff, (*argbColor >> 8) & 0xff, (*argbColor) & 0xff);
    if (ChooseColor(&cc)) {
        *argbColor = 0xFF000000 | ((unsigned int)GetRValue(cc.rgbResult) << 16) | ((unsigned int)GetGValue(cc.rgbResult) << 8) | (unsigned int)GetBValue(cc.rgbResult);
    }
    return true;
}

static bool HwsMfcChooseBmpFile(HWND hwnd, wchar_t* path, size_t pathChars)
{
    if (!path || pathChars == 0) return false;
    OPENFILENAMEW ofn = {};
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = L"Bitmap Files (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0";
    ofn.lpstrFile = path;
    ofn.nMaxFile = (DWORD)pathChars;
    ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
    ofn.lpstrDefExt = L"bmp";
    return GetOpenFileNameW(&ofn) ? true : false;
}

static LRESULT CALLBACK HwsMfcOsdDialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    HwsMfcOsdDialogContext* ctx = (HwsMfcOsdDialogContext*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    switch (msg) {
    case WM_CREATE:
    {
        CREATESTRUCTA* cs = (CREATESTRUCTA*)lp;
        ctx = (HwsMfcOsdDialogContext*)cs->lpCreateParams;
        SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)ctx);
        if (!ctx->imageMode) {
            CreateWindowW(L"STATIC", L"Text", WS_CHILD | WS_VISIBLE, 18, 22, 110, 22, hwnd, NULL, NULL, NULL);
            CreateWindowW(L"EDIT", ctx->item.text[0] ? ctx->item.text : L"hwscapture OSD", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL, 125, 18, 360, 24, hwnd, (HMENU)IDC_OSD_TEXT_EDIT, NULL, NULL);
            CreateWindowW(L"STATIC", L"Font", WS_CHILD | WS_VISIBLE, 18, 180, 90, 22, hwnd, NULL, NULL, NULL);
            HWND fontCombo = CreateWindowW(L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL, 125, 176, 180, 220, hwnd, (HMENU)IDC_OSD_FONT_NAME, NULL, NULL);
            SendMessageW(fontCombo, CB_ADDSTRING, 0, (LPARAM)L"Microsoft YaHei UI");
            SendMessageW(fontCombo, CB_ADDSTRING, 0, (LPARAM)L"Microsoft YaHei");
            SendMessageW(fontCombo, CB_ADDSTRING, 0, (LPARAM)L"SimSun");
            SendMessageW(fontCombo, CB_ADDSTRING, 0, (LPARAM)L"Segoe UI");
            SendMessageW(fontCombo, CB_ADDSTRING, 0, (LPARAM)L"Arial");
            SetWindowTextW(fontCombo, ctx->item.fontName[0] ? ctx->item.fontName : L"Microsoft YaHei UI");
            CreateWindowW(L"STATIC", L"Size", WS_CHILD | WS_VISIBLE, 318, 180, 42, 22, hwnd, NULL, NULL, NULL);
            CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NUMBER, 360, 176, 55, 24, hwnd, (HMENU)IDC_OSD_FONT_EDIT, NULL, NULL);
            HwsMfcSetDlgIntValue(hwnd, IDC_OSD_FONT_EDIT, ctx->item.fontSize > 0 ? ctx->item.fontSize : 36);
            CreateWindowA("BUTTON", "Text Color", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 430, 176, 105, 28, hwnd, (HMENU)IDC_OSD_TEXT_COLOR, NULL, NULL);
            CreateWindowA("BUTTON", "Background Color", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 125, 212, 135, 28, hwnd, (HMENU)IDC_OSD_BG_COLOR, NULL, NULL);
            CreateWindowA("BUTTON", "Transparent Background", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 280, 212, 200, 24, hwnd, (HMENU)IDC_OSD_TRANSPARENT, NULL, NULL);
            SendMessageA(GetDlgItem(hwnd, IDC_OSD_TRANSPARENT), BM_SETCHECK, ctx->item.transparentBackground ? BST_CHECKED : BST_UNCHECKED, 0);
        } else {
            CreateWindowA("STATIC", "BMP File", WS_CHILD | WS_VISIBLE, 18, 22, 110, 22, hwnd, NULL, NULL, NULL);
            CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL, 125, 18, 360, 24, hwnd, (HMENU)IDC_OSD_FILE_EDIT, NULL, NULL);
            CreateWindowA("BUTTON", "Browse...", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 495, 17, 90, 26, hwnd, (HMENU)IDC_OSD_BROWSE_FILE, NULL, NULL);
            if (ctx->item.filePath[0]) SetWindowTextW(GetDlgItem(hwnd, IDC_OSD_FILE_EDIT), ctx->item.filePath);
            CreateWindowA("BUTTON", "Transparent Background Color", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 125, 176, 230, 24, hwnd, (HMENU)IDC_OSD_TRANSPARENT, NULL, NULL);
            SendMessageA(GetDlgItem(hwnd, IDC_OSD_TRANSPARENT), BM_SETCHECK, ctx->item.transparentBackground ? BST_CHECKED : BST_UNCHECKED, 0);
            CreateWindowA("BUTTON", "Choose Key Color", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 370, 172, 135, 28, hwnd, (HMENU)IDC_OSD_BG_COLOR, NULL, NULL);
        }
        CreateWindowA("STATIC", "X", WS_CHILD | WS_VISIBLE, 18, 70, 30, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NUMBER, 55, 66, 80, 24, hwnd, (HMENU)IDC_OSD_X_EDIT, NULL, NULL);
        CreateWindowA("STATIC", "Y", WS_CHILD | WS_VISIBLE, 150, 70, 30, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NUMBER, 185, 66, 80, 24, hwnd, (HMENU)IDC_OSD_Y_EDIT, NULL, NULL);
        CreateWindowA("STATIC", "Width", WS_CHILD | WS_VISIBLE, 18, 110, 50, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NUMBER, 80, 106, 80, 24, hwnd, (HMENU)IDC_OSD_W_EDIT, NULL, NULL);
        CreateWindowA("STATIC", "Height", WS_CHILD | WS_VISIBLE, 180, 110, 55, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NUMBER, 245, 106, 80, 24, hwnd, (HMENU)IDC_OSD_H_EDIT, NULL, NULL);
        CreateWindowA("STATIC", "Opacity 0.0-1.0", WS_CHILD | WS_VISIBLE, 18, 145, 110, 22, hwnd, NULL, NULL, NULL);
        CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, 145, 141, 80, 24, hwnd, (HMENU)IDC_OSD_OPACITY_EDIT, NULL, NULL);
        HwsMfcSetDlgIntValue(hwnd, IDC_OSD_X_EDIT, ctx->item.x);
        HwsMfcSetDlgIntValue(hwnd, IDC_OSD_Y_EDIT, ctx->item.y);
        HwsMfcSetDlgIntValue(hwnd, IDC_OSD_W_EDIT, ctx->item.width > 0 ? ctx->item.width : (ctx->imageMode ? 160 : 400));
        HwsMfcSetDlgIntValue(hwnd, IDC_OSD_H_EDIT, ctx->item.height > 0 ? ctx->item.height : (ctx->imageMode ? 120 : 80));
        HwsMfcSetDlgFloatValue(hwnd, IDC_OSD_OPACITY_EDIT, ctx->item.opacity > 0.0f ? ctx->item.opacity : 1.0f);
        CreateWindowA("BUTTON", "OK", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 395, 250, 90, 30, hwnd, (HMENU)IDC_OK_BUTTON, NULL, NULL);
        CreateWindowA("BUTTON", "Cancel", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 500, 250, 90, 30, hwnd, (HMENU)IDC_CANCEL_BUTTON, NULL, NULL);
        HwsMfcCenterWindowToParent(hwnd, GetParent(hwnd));
        return 0;
    }
    case WM_COMMAND:
        switch (LOWORD(wp)) {
        case IDC_OSD_TEXT_COLOR: if (ctx) HwsMfcChooseRgbColor(hwnd, &ctx->item.color); return 0;
        case IDC_OSD_BG_COLOR: if (ctx) HwsMfcChooseRgbColor(hwnd, &ctx->item.backgroundColor); return 0;
        case IDC_OSD_BROWSE_FILE:
            if (ctx && HwsMfcChooseBmpFile(hwnd, ctx->item.filePath, MAX_PATH)) SetWindowTextW(GetDlgItem(hwnd, IDC_OSD_FILE_EDIT), ctx->item.filePath);
            return 0;
        case IDC_OK_BUTTON:
        case IDOK:
            if (ctx) {
                ctx->item.enable = 1;
                ctx->item.x = HwsMfcGetDlgIntValue(hwnd, IDC_OSD_X_EDIT, 0);
                ctx->item.y = HwsMfcGetDlgIntValue(hwnd, IDC_OSD_Y_EDIT, 0);
                ctx->item.width = HwsMfcGetDlgIntValue(hwnd, IDC_OSD_W_EDIT, 0);
                ctx->item.height = HwsMfcGetDlgIntValue(hwnd, IDC_OSD_H_EDIT, 0);
                ctx->item.opacity = HwsMfcGetDlgFloatValue(hwnd, IDC_OSD_OPACITY_EDIT, 1.0f);
                ctx->item.transparentBackground = (SendMessageA(GetDlgItem(hwnd, IDC_OSD_TRANSPARENT), BM_GETCHECK, 0, 0) == BST_CHECKED) ? 1 : 0;
                if (!ctx->imageMode) {
                    GetWindowTextW(GetDlgItem(hwnd, IDC_OSD_TEXT_EDIT), ctx->item.text, 512);
                    GetWindowTextW(GetDlgItem(hwnd, IDC_OSD_FONT_NAME), ctx->item.fontName, 64);
                    if (!ctx->item.fontName[0]) wcscpy_s(ctx->item.fontName, L"Microsoft YaHei UI");
                    ctx->item.fontSize = HwsMfcGetDlgIntValue(hwnd, IDC_OSD_FONT_EDIT, 36);
                    ctx->item.filePath[0] = 0;
                } else {
                    GetWindowTextW(GetDlgItem(hwnd, IDC_OSD_FILE_EDIT), ctx->item.filePath, MAX_PATH);
                    ctx->item.text[0] = 0;
                    ctx->item.fontSize = 0;
                    if (!ctx->item.filePath[0]) { MessageBoxA(hwnd, "Please select a BMP file.", "hwscapture", MB_ICONWARNING | MB_OK); return 0; }
                }
                ctx->accepted = true;
            }
            DestroyWindow(hwnd);
            return 0;
        case IDC_CANCEL_BUTTON:
        case IDCANCEL:
            DestroyWindow(hwnd);
            return 0;
        }
        break;
    case WM_CLOSE: DestroyWindow(hwnd); return 0;
    }
    return DefWindowProcA(hwnd, msg, wp, lp);
}

static bool HwsMfcShowOsdDialog(HWND parent, bool imageMode, HWS_OSD_ITEM* item)
{
    HINSTANCE hInst = (HINSTANCE)GetModuleHandleA(NULL);
    WNDCLASSA wc = {};
    wc.lpfnWndProc = HwsMfcOsdDialogProc;
    wc.hInstance = hInst;
    wc.lpszClassName = imageMode ? "HwsMfcImageOsdDialogV331" : "HwsMfcTextOsdDialogV331";
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    RegisterClassA(&wc);
    HwsMfcOsdDialogContext ctx = {};
    ctx.imageMode = imageMode;
    if (item) ctx.item = *item;
    if (ctx.item.opacity <= 0.0f) ctx.item.opacity = 1.0f;
    if (ctx.item.color == 0) ctx.item.color = 0xFFFFFFFF;
    if (ctx.item.backgroundColor == 0) ctx.item.backgroundColor = 0xFF000000;
    if (!imageMode && ctx.item.fontSize <= 0) ctx.item.fontSize = 36;
    HWND dlg = CreateWindowExA(WS_EX_DLGMODALFRAME, wc.lpszClassName, imageMode ? "Image OSD" : "Text OSD",
        WS_CAPTION | WS_SYSMENU | WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, imageMode ? 620 : 520, 330,
        parent, NULL, hInst, &ctx);
    if (!dlg) return false;
    EnableWindow(parent, FALSE);
    ShowWindow(dlg, SW_SHOW);
    UpdateWindow(dlg);
    MSG msg;
    while (IsWindow(dlg) && GetMessageA(&msg, NULL, 0, 0)) {
        if (!IsDialogMessageA(dlg, &msg)) { TranslateMessage(&msg); DispatchMessageA(&msg); }
    }
    EnableWindow(parent, TRUE);
    SetForegroundWindow(parent);
    if (ctx.accepted && item) { *item = ctx.item; return true; }
    return false;
}

class CCropDlg : public CDialogEx
{
public:
    HWS_VIDEO_CROP_RECT rect = {0,0,640,480};
    CCropDlg(CWnd* p, const HWS_VIDEO_CROP_RECT& r) : CDialogEx(IDD_CROP_DIALOG, p), rect(r) {}
    BOOL OnInitDialog() override
    {
        CDialogEx::OnInitDialog();
        SetDlgItemInt(IDC_EDIT_X, rect.x); SetDlgItemInt(IDC_EDIT_Y, rect.y); SetDlgItemInt(IDC_EDIT_W, rect.width); SetDlgItemInt(IDC_EDIT_H, rect.height);
        return TRUE;
    }
    void OnOK() override
    {
        BOOL ok = FALSE;
        rect.x = GetDlgItemInt(IDC_EDIT_X, &ok, FALSE); rect.y = GetDlgItemInt(IDC_EDIT_Y, &ok, FALSE);
        rect.width = GetDlgItemInt(IDC_EDIT_W, &ok, FALSE); rect.height = GetDlgItemInt(IDC_EDIT_H, &ok, FALSE);
        if (rect.width > 0 && rect.height > 0) CDialogEx::OnOK();
    }
};

BEGIN_MESSAGE_MAP(CHwsCaptureMainDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_ERASEBKGND()
    ON_WM_SIZE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_RBUTTONUP()
    ON_MESSAGE(WM_HWS_PREVIEW_CLICK, &CHwsCaptureMainDlg::OnPreviewClick)
    ON_MESSAGE(WM_HWS_PREVIEW_DBLCLICK, &CHwsCaptureMainDlg::OnPreviewDblClick)
    ON_MESSAGE(WM_HWS_PREVIEW_RBUTTON, &CHwsCaptureMainDlg::OnPreviewRButton)
    ON_WM_TIMER()
    ON_WM_DESTROY()
    ON_WM_CLOSE()
    ON_COMMAND(IDM_SOURCE_VIDEO, &CHwsCaptureMainDlg::OnSourceVideo)
    ON_COMMAND(IDM_SOURCE_AUDIO, &CHwsCaptureMainDlg::OnSourceAudio)
    ON_COMMAND(IDM_SOURCE_CLOSE_ALL, &CHwsCaptureMainDlg::OnSourceCloseAll)
    ON_COMMAND(IDM_SOURCE_CLOSE_TEST, &CHwsCaptureMainDlg::OnSourceSimpleTest)
    ON_COMMAND(IDM_VIEW_1, &CHwsCaptureMainDlg::OnView1)
    ON_COMMAND(IDM_VIEW_4, &CHwsCaptureMainDlg::OnView4)
    ON_COMMAND(IDM_VIEW_8, &CHwsCaptureMainDlg::OnView8)
    ON_COMMAND(IDM_VIEW_16, &CHwsCaptureMainDlg::OnView16)
    ON_COMMAND(IDM_OSD_TEXT, &CHwsCaptureMainDlg::OnOsdText)
    ON_COMMAND(IDM_OSD_IMAGE, &CHwsCaptureMainDlg::OnOsdImage)
    ON_COMMAND(IDM_OSD_DATETIME, &CHwsCaptureMainDlg::OnOsdDateTime)
    ON_COMMAND(IDM_OSD_REMOVE_LAST, &CHwsCaptureMainDlg::OnOsdRemoveLast)
    ON_COMMAND(IDM_OSD_CLEAR, &CHwsCaptureMainDlg::OnOsdClear)
    ON_COMMAND(IDM_REC_START, &CHwsCaptureMainDlg::OnRecorderStart)
    ON_COMMAND(IDM_REC_STOP, &CHwsCaptureMainDlg::OnRecorderStop)
    ON_COMMAND(IDM_REC_VIDEO_CB, &CHwsCaptureMainDlg::OnRecorderVideoCallback)
    ON_COMMAND(IDM_REC_AUDIO_CB, &CHwsCaptureMainDlg::OnRecorderAudioCallback)
    ON_COMMAND(IDM_SNAP_SETTINGS, &CHwsCaptureMainDlg::OnSnapshotSettings)
    ON_COMMAND(IDM_SNAP_CAPTURE, &CHwsCaptureMainDlg::OnSnapshotCapture)
    ON_COMMAND(IDM_CROP_ADD, &CHwsCaptureMainDlg::OnCropAdd)
    ON_COMMAND(IDM_CROP_REMOVE, &CHwsCaptureMainDlg::OnCropRemove)
    ON_COMMAND(IDM_AUDIO_MONITOR_ON, &CHwsCaptureMainDlg::OnAudioMonitorOn)
    ON_COMMAND(IDM_AUDIO_MONITOR_OFF, &CHwsCaptureMainDlg::OnAudioMonitorOff)
    ON_COMMAND(IDM_EXIT_APP, &CHwsCaptureMainDlg::OnExitApp)
    ON_COMMAND(IDM_CTX_VIDEO_SOURCE, &CHwsCaptureMainDlg::OnContextVideoSource)
    ON_COMMAND(IDM_CTX_AUDIO_SOURCE, &CHwsCaptureMainDlg::OnContextAudioSource)
    ON_COMMAND(IDM_CTX_AUDIO_MONITOR_ON, &CHwsCaptureMainDlg::OnContextAudioMonitorOn)
    ON_COMMAND(IDM_CTX_AUDIO_MONITOR_OFF, &CHwsCaptureMainDlg::OnContextAudioMonitorOff)
    ON_COMMAND(IDM_CTX_REMOVE_AVIDEO, &CHwsCaptureMainDlg::OnContextRemoveVideo)
END_MESSAGE_MAP()

CHwsCaptureMainDlg::CHwsCaptureMainDlg(CWnd* parent) : CDialogEx(IDD_MAIN_DIALOG, parent) {}

BOOL CHwsCaptureMainDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    BuildMainMenu();
    if (!m_mgr.Initialize()) { AfxMessageBox(L"HWS_Init failed.", MB_ICONERROR); EndDialog(IDCANCEL); return FALSE; }

    // Create all preview panes once. View switching must only change layout/visibility.
    // It must not stop/restart video ports, recreate renderers, or re-enumerate devices.
    EnsurePreviewPanes(MAX_PREVIEW_PANES);

    SetViewCount(1);
    SetTimer(1, 1000, NULL);
    UpdateTitle();
    return TRUE;
}

void CHwsCaptureMainDlg::BuildMainMenu()
{
    // Use raw Win32 menu handles here instead of detached temporary CMenu objects.
    // This keeps ownership explicit and avoids MFC debug handle-map allocations being
    // reported as leaks by VLD/CRT at process shutdown.
    if (m_hMainMenu) {
        ::SetMenu(GetSafeHwnd(), NULL);
        ::DestroyMenu(m_hMainMenu);
        m_hMainMenu = NULL;
    }

    HMENU source = ::CreatePopupMenu();
    ::AppendMenuW(source, MF_STRING, IDM_SOURCE_VIDEO, L"Add Video Capture Device...");
    ::AppendMenuW(source, MF_STRING, IDM_SOURCE_AUDIO, L"Add Audio Input Device...");
    ::AppendMenuW(source, MF_SEPARATOR, 0, NULL);
    ::AppendMenuW(source, MF_STRING, IDM_SOURCE_CLOSE_ALL, L"Stop All Preview");
    ::AppendMenuW(source, MF_SEPARATOR, 0, NULL);
    ::AppendMenuW(source, MF_STRING, IDM_SOURCE_CLOSE_TEST, L"Simple Test");
    
    HMENU view = ::CreatePopupMenu();
    ::AppendMenuW(view, MF_STRING, IDM_VIEW_1, L"1 View");
    ::AppendMenuW(view, MF_STRING, IDM_VIEW_4, L"4 Views");
    ::AppendMenuW(view, MF_STRING, IDM_VIEW_8, L"8 Views");
    ::AppendMenuW(view, MF_STRING, IDM_VIEW_16, L"16 Views");

    HMENU osd = ::CreatePopupMenu();
    ::AppendMenuW(osd, MF_STRING, IDM_OSD_TEXT, L"Add Text OSD...");
    ::AppendMenuW(osd, MF_STRING, IDM_OSD_IMAGE, L"Add Image OSD...");
    ::AppendMenuW(osd, MF_STRING, IDM_OSD_DATETIME, L"Add Date/Time OSD");
    ::AppendMenuW(osd, MF_STRING, IDM_OSD_REMOVE_LAST, L"Remove Last OSD");
    ::AppendMenuW(osd, MF_STRING, IDM_OSD_CLEAR, L"Clear All OSD");

    HMENU rec = ::CreatePopupMenu();
    ::AppendMenuW(rec, MF_STRING, IDM_REC_START, L"Start Recording...");
    ::AppendMenuW(rec, MF_STRING, IDM_REC_STOP, L"Stop Recording");
    ::AppendMenuW(rec, MF_SEPARATOR, 0, NULL);
    ::AppendMenuW(rec, MF_STRING, IDM_REC_VIDEO_CB, L"Enable Video Encoded Callback");
    ::AppendMenuW(rec, MF_STRING, IDM_REC_AUDIO_CB, L"Enable Audio Encoded Callback");

    HMENU snap = ::CreatePopupMenu();
    ::AppendMenuW(snap, MF_STRING, IDM_SNAP_SETTINGS, L"Snapshot Settings...");
    ::AppendMenuW(snap, MF_STRING, IDM_SNAP_CAPTURE, L"Capture Snapshot");

    HMENU crop = ::CreatePopupMenu();
    ::AppendMenuW(crop, MF_STRING, IDM_CROP_ADD, L"Add Video Crop...");
    ::AppendMenuW(crop, MF_STRING, IDM_CROP_REMOVE, L"Remove Video Crop");

    HMENU file = ::CreatePopupMenu();
    ::AppendMenuW(file, MF_STRING, IDM_EXIT_APP, L"Exit");

    m_hMainMenu = ::CreateMenu();
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)source, L"Source");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)view, L"View");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)osd, L"OSD");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)rec, L"Recorder");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)snap, L"Snapshot");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)crop, L"Video Crop");
    ::AppendMenuW(m_hMainMenu, MF_POPUP, (UINT_PTR)file, L"File");

    ::SetMenu(GetSafeHwnd(), m_hMainMenu);
}

void CHwsCaptureMainDlg::EnsurePreviewPanes(int count)
{
    if ((int)m_panes.size() >= count) return;

    std::vector<HWND> hwnds;
    hwnds.reserve((size_t)count);

    for (int i = 0; i < count; ++i) {
        CPreviewPane* p = nullptr;
        if (i < (int)m_panes.size()) {
            p = m_panes[(size_t)i];
        } else {
            p = new CPreviewPane();
            if (p->CreatePane(this, i)) {
                m_panes.push_back(p);
            } else {
                delete p;
                continue;
            }
        }
        p->m_selected = (i == m_selectedSlot);
        hwnds.push_back(p->GetSafeHwnd());
    }

    // This is called only once during dialog initialization. It must not be called
    // during View 1/4/8/16 switching because it rebuilds SDK slot state.
    m_mgr.SetSlotWindows(hwnds);
    m_mgr.SelectSlot(m_selectedSlot);
}

void CHwsCaptureMainDlg::SetViewCount(int count)
{
    if (count != 1 && count != 4 && count != 8 && count != 16) count = 1;

    EnsurePreviewPanes(MAX_PREVIEW_PANES);

    m_viewCount = count;
    m_fullscreenSlot = -1;

    if (m_selectedSlot < 0 || m_selectedSlot >= MAX_PREVIEW_PANES) {
        m_selectedSlot = 0;
    }

    SelectSlot(m_selectedSlot);
    LayoutSlots();
    UpdateTitle();
}

void CHwsCaptureMainDlg::LayoutSlots()
{
    if (m_panes.empty()) return;
    CRect rc; GetClientRect(&rc);
    int top = 0; rc.top += top;

    // Fullscreen is a UI-only operation. All hidden channels keep running.
    if (m_fullscreenSlot >= 0 && m_fullscreenSlot < (int)m_panes.size()) {
        for (int i = 0; i < (int)m_panes.size(); ++i) {
            m_panes[(size_t)i]->ShowWindow(i == m_fullscreenSlot ? SW_SHOW : SW_HIDE);
        }
        m_panes[(size_t)m_fullscreenSlot]->MoveWindow(rc);
        return;
    }

    int cols = 1, rows = 1;
    if (m_viewCount == 4) { cols = 2; rows = 2; }
    else if (m_viewCount == 8) { cols = 4; rows = 2; }
    else if (m_viewCount == 16) { cols = 4; rows = 4; }

    const int visibleCount = std::min(m_viewCount, (int)m_panes.size());
    const int w = std::max(1, rc.Width() / cols);
    const int h = std::max(1, rc.Height() / rows);

    for (int i = 0; i < (int)m_panes.size(); ++i) {
        CPreviewPane* pane = m_panes[(size_t)i];
        if (!pane) continue;

        if (i >= visibleCount) {
            pane->ShowWindow(SW_HIDE);
            continue;
        }

        int r = i / cols;
        int c = i % cols;
        pane->ShowWindow(SW_SHOW);
        pane->MoveWindow(c * w, rc.top + r * h,
            (c == cols - 1) ? rc.Width() - c * w : w,
            (r == rows - 1) ? rc.Height() - r * h : h);
        pane->Invalidate(FALSE);
    }
}

int CHwsCaptureMainDlg::HitTestSlot(CPoint pt) const
{
    for (int i = 0; i < (int)m_panes.size(); ++i) {
        CPreviewPane* pane = m_panes[(size_t)i];
        if (!pane || !pane->IsWindowVisible()) continue;
        CRect rc;
        pane->GetWindowRect(&rc);
        ScreenToClient(&rc);
        if (rc.PtInRect(pt)) return i;
    }
    return -1;
}
void CHwsCaptureMainDlg::SelectSlot(int index)
{
    if (index < 0 || index >= (int)m_panes.size()) return;
    m_selectedSlot = index; m_mgr.SelectSlot(index);
    for (int i = 0; i < (int)m_panes.size(); ++i) { m_panes[(size_t)i]->m_selected = (i == index); m_panes[(size_t)i]->Invalidate(); }
    UpdateTitle();
}
void CHwsCaptureMainDlg::UpdateTitle()
{
    CString s;
    if (m_fullscreenSlot >= 0)
        s.Format(L"hwscapture_mfc_demo - view=%d selected=%d fullscreen=%d", m_viewCount, m_selectedSlot + 1, m_fullscreenSlot + 1);
    else
        s.Format(L"hwscapture_mfc_demo - view=%d selected=%d", m_viewCount, m_selectedSlot + 1);
    SetWindowTextW(s);
}

bool CHwsCaptureMainDlg::ShowVideoSourceDialog(HWS_VIDEO_INPUT_CONFIG& cfg)
{
    // Keep zero/invalid config for a new source so the dialog can select the
    // capture device's first/default capability. Do not replace it with the
    // demo fallback 1280x720/30 here, otherwise the combo boxes show the demo
    // default instead of the card/driver default.
    return HwsMfcShowVideoCaptureDeviceDialog(GetSafeHwnd(), &cfg);
}
bool CHwsCaptureMainDlg::ShowAudioSourceDialog(int& deviceIndex)
{
    CAudioSourceDlg dlg(this);
    const INT_PTR ret = dlg.DoModal();
    if (ret == IDOK) { deviceIndex = dlg.deviceIndex; return true; }
    return false;
}
bool CHwsCaptureMainDlg::ShowCropDialog(HWS_VIDEO_CROP_RECT& rc)
{
    CCropDlg dlg(this, rc);
    const INT_PTR ret = dlg.DoModal();
    if (ret == IDOK) { rc = dlg.rect; return true; }
    return false;
}

void CHwsCaptureMainDlg::DestroyPreviewPanes()
{
    for (CPreviewPane* pane : m_panes) {
        if (!pane) continue;
        if (::IsWindow(pane->GetSafeHwnd())) {
            pane->DestroyWindow();
        }
        delete pane;
    }
    m_panes.clear();
}

void CHwsCaptureMainDlg::SafeShutdown()
{
    if (m_shutdownDone) return;
    m_shutdownDone = true;

    KillTimer(1);
    m_mgr.Shutdown();
    DestroyPreviewPanes();
    if (m_hMainMenu) {
        ::SetMenu(GetSafeHwnd(), NULL);
        ::DrawMenuBar(GetSafeHwnd());
        ::DestroyMenu(m_hMainMenu);
        m_hMainMenu = NULL;
    }
}

void CHwsCaptureMainDlg::OnCancel() { SafeShutdown(); CDialogEx::OnCancel(); }
void CHwsCaptureMainDlg::OnClose() { SafeShutdown(); CDialogEx::OnClose(); }
void CHwsCaptureMainDlg::OnDestroy() { SafeShutdown(); CDialogEx::OnDestroy(); }
BOOL CHwsCaptureMainDlg::OnEraseBkgnd(CDC*) { return TRUE; }
void CHwsCaptureMainDlg::OnPaint() { CPaintDC dc(this); }
void CHwsCaptureMainDlg::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); LayoutSlots(); }
void CHwsCaptureMainDlg::OnLButtonDown(UINT nFlags, CPoint pt) { int s = HitTestSlot(pt); if (s >= 0) SelectSlot(s); CDialogEx::OnLButtonDown(nFlags, pt); }
void CHwsCaptureMainDlg::OnLButtonDblClk(UINT nFlags, CPoint pt) { int s = HitTestSlot(pt); if (s >= 0) ToggleFullscreenSlot(s); CDialogEx::OnLButtonDblClk(nFlags, pt); }
void CHwsCaptureMainDlg::OnRButtonUp(UINT nFlags, CPoint pt) { int s = HitTestSlot(pt); if (s >= 0) ShowSlotContextMenu(s, pt); CDialogEx::OnRButtonUp(nFlags, pt); }

LRESULT CHwsCaptureMainDlg::OnPreviewClick(WPARAM wParam, LPARAM)
{
    SelectSlot((int)wParam);
    return 0;
}

LRESULT CHwsCaptureMainDlg::OnPreviewDblClick(WPARAM wParam, LPARAM)
{
    ToggleFullscreenSlot((int)wParam);
    return 0;
}

LRESULT CHwsCaptureMainDlg::OnPreviewRButton(WPARAM wParam, LPARAM lParam)
{
    CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    ShowSlotContextMenu((int)wParam, pt);
    return 0;
}

void CHwsCaptureMainDlg::ShowSlotContextMenu(int index, CPoint ptClient)
{
    SelectSlot(index);
    CMenu menu;
    menu.CreatePopupMenu();
    menu.AppendMenuW(MF_STRING, IDM_CTX_VIDEO_SOURCE, L"Select Video Source...");
    menu.AppendMenuW(MF_STRING, IDM_CTX_AUDIO_SOURCE, L"Select Audio Source...");
    menu.AppendMenuW(MF_STRING, IDM_CTX_REMOVE_AVIDEO, L"Remove Video/Audio Source");
    menu.AppendMenuW(MF_SEPARATOR);
    menu.AppendMenuW(MF_STRING, IDM_OSD_TEXT, L"Add Text OSD...");
    menu.AppendMenuW(MF_STRING, IDM_OSD_IMAGE, L"Add Image OSD...");
    menu.AppendMenuW(MF_STRING, IDM_OSD_DATETIME, L"Add Date/Time OSD");
    menu.AppendMenuW(MF_STRING, IDM_OSD_REMOVE_LAST, L"Remove Last OSD");
    menu.AppendMenuW(MF_STRING, IDM_OSD_CLEAR, L"Clear All OSD");
    menu.AppendMenuW(MF_SEPARATOR);
    menu.AppendMenuW(MF_STRING, IDM_CROP_ADD, L"Add Video Crop...");
    menu.AppendMenuW(MF_STRING, IDM_CROP_REMOVE, L"Remove Video Crop");
    menu.AppendMenuW(MF_SEPARATOR);
    menu.AppendMenuW(MF_STRING, IDM_SNAP_CAPTURE, L"Capture Snapshot");
    menu.AppendMenuW(MF_STRING, IDM_REC_START, L"Start Recording...");
    menu.AppendMenuW(MF_STRING, IDM_REC_STOP, L"Stop Recording");
    menu.AppendMenuW(MF_SEPARATOR);
    menu.AppendMenuW(MF_STRING, IDM_CTX_AUDIO_MONITOR_ON, L"Enable Audio Monitor");
    menu.AppendMenuW(MF_STRING, IDM_CTX_AUDIO_MONITOR_OFF, L"Disable Audio Monitor");
    ClientToScreen(&ptClient);
    menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, ptClient.x, ptClient.y, this);
    menu.DestroyMenu();
}

void CHwsCaptureMainDlg::ToggleFullscreenSlot(int index)
{
    if (index < 0 || index >= (int)m_panes.size()) return;
    m_fullscreenSlot = (m_fullscreenSlot == index) ? -1 : index;
    SelectSlot(index);
    LayoutSlots();
}
void CHwsCaptureMainDlg::OnTimer(UINT_PTR id) { if (id == 1) m_mgr.UpdateDateTimeOsd(); CDialogEx::OnTimer(id); }

void CHwsCaptureMainDlg::OnSourceVideo()
{
    HWS_VIDEO_INPUT_CONFIG cfg = {};

    // Match hwscapture_test behavior:
    // - Existing active slot: show the current slot configuration.
    // - Empty slot: leave cfg zeroed so the dialog selects the device/driver
    //   default capability instead of the demo fallback value.
    if (CaptureManager::Slot* slot = m_mgr.GetSlot(m_selectedSlot)) {
        if (slot->active) {
            cfg = slot->config;
        }
    }

    if (ShowVideoSourceDialog(cfg)) {
        m_mgr.AddVideoSource(m_hWnd, m_selectedSlot, cfg);
        if (m_selectedSlot < (int)m_panes.size()) {
            m_panes[(size_t)m_selectedSlot]->m_active = true;
            m_panes[(size_t)m_selectedSlot]->Invalidate();
        }
    }
}
void CHwsCaptureMainDlg::OnSourceAudio() { int d = 0; if (ShowAudioSourceDialog(d)) m_mgr.AddAudioSource(m_hWnd, m_selectedSlot, d); }
void CHwsCaptureMainDlg::OnSourceCloseAll()
{
    m_mgr.StopAll();
    for (CPreviewPane* pane : m_panes) {
        if (pane) {
            pane->m_active = false;
            pane->Invalidate(FALSE);
        }
    }
}
void CHwsCaptureMainDlg::OnSourceSimpleTest()
{
    HWS_PORT g_videoInput;
    HWS_PORT g_videoRender;

    g_videoInput = HWS_CreateVideoInput();
    g_videoRender = HWS_CreateVideoRender(this->m_hWnd);
    if (!g_videoInput || !g_videoRender) {
        return ;
    }

    HWS_VIDEO_INPUT_CONFIG cfg = {};
    cfg.deviceIndex = 0;
    cfg.width = 1280;
    cfg.height = 720;
    cfg.fps = 30;
    cfg.pixelFormat = HWS_PIXFMT_YUY2;
    cfg.colorSpace = HWS_COLOR_SPACE_AUTO;
    cfg.colorRange = HWS_COLOR_RANGE_AUTO;
    cfg.bufferCount = 4;

    HWS_VIDEO_RENDER_CONFIG rcfg = {};
    rcfg.width = cfg.width;
    rcfg.height = cfg.height;
    rcfg.pixelFormat = cfg.pixelFormat;

    if (HWS_SetVideoInputConfig(g_videoInput, &cfg) != HWS_OK) {
        return ;
    }

    HWS_SetVideoRenderConfig(g_videoRender, &rcfg);

    if (HWS_Connect(g_videoInput, g_videoRender) != HWS_OK) {
  
        return ;
    }

    if (HWS_Start(g_videoInput) != HWS_OK) {
  
        return ;
    }

    if (g_videoInput) {
        HWS_Stop(g_videoInput);
    }
    if (g_videoInput && g_videoRender) {
        HWS_Disconnect(g_videoInput, g_videoRender);
    }
    if (g_videoRender) {
        HWS_DestroyPort(g_videoRender);
        g_videoRender = nullptr;
    }
    if (g_videoInput) {
        HWS_DestroyPort(g_videoInput);
        g_videoInput = nullptr;
    }
}
void CHwsCaptureMainDlg::OnView1() { SetViewCount(1); }
void CHwsCaptureMainDlg::OnView4() { SetViewCount(4); }
void CHwsCaptureMainDlg::OnView8() { SetViewCount(8); }
void CHwsCaptureMainDlg::OnView16() { SetViewCount(16); }
void CHwsCaptureMainDlg::OnOsdText()
{
    HWS_OSD_ITEM item = {};
    item.id = 0;
    item.enable = 1;
    item.type = HWS_OSD_ITEM_TEXT;
    item.zOrder = 0;
    item.x = 20;
    item.y = 20;
    item.width = 400;
    item.height = 80;
    item.opacity = 1.0f;
    item.color = 0xFFFFFFFF;
    item.backgroundColor = 0x80000000;
    item.transparentBackground = 0;
    item.fontSize = 36;
    wcscpy_s(item.fontName, L"Microsoft YaHei UI");
    wcscpy_s(item.text, L"hwscapture OSD");
    if (HwsMfcShowOsdDialog(GetSafeHwnd(), false, &item)) {
        if (!m_mgr.AddOsdItem(m_selectedSlot, item)) AfxMessageBox(L"Please add/start a video source before adding OSD.", MB_ICONINFORMATION);
    }
}

void CHwsCaptureMainDlg::OnOsdImage()
{
    HWS_OSD_ITEM item = {};
    item.id = 0;
    item.enable = 1;
    item.type = HWS_OSD_ITEM_IMAGE;
    item.zOrder = 0;
    item.x = 20;
    item.y = 20;
    item.width = 160;
    item.height = 120;
    item.opacity = 1.0f;
    item.color = 0xFFFFFFFF;
    item.backgroundColor = 0x80000000;
    item.transparentBackground = 1;
    if (HwsMfcShowOsdDialog(GetSafeHwnd(), true, &item)) {
        if (!m_mgr.AddOsdItem(m_selectedSlot, item)) AfxMessageBox(L"Please add/start a video source before adding OSD.", MB_ICONINFORMATION);
    }
}
void CHwsCaptureMainDlg::OnOsdDateTime() { m_mgr.AddDateTimeOsd(m_selectedSlot); }
void CHwsCaptureMainDlg::OnOsdRemoveLast() { m_mgr.RemoveLastOsd(m_selectedSlot); }
void CHwsCaptureMainDlg::OnOsdClear() { m_mgr.ClearOsd(m_selectedSlot); }
void CHwsCaptureMainDlg::OnRecorderStart() { m_mgr.StartRecorder(m_hWnd, m_selectedSlot); }
void CHwsCaptureMainDlg::OnRecorderStop() { m_mgr.StopRecorder(m_selectedSlot); }
void CHwsCaptureMainDlg::OnRecorderVideoCallback() { m_mgr.ToggleRecorderVideoCallback(); }
void CHwsCaptureMainDlg::OnRecorderAudioCallback() { m_mgr.ToggleRecorderAudioCallback(); }
void CHwsCaptureMainDlg::OnSnapshotSettings() { m_mgr.ConfigureSnapshot(m_hWnd, m_selectedSlot); }
void CHwsCaptureMainDlg::OnSnapshotCapture() { m_mgr.CaptureSnapshot(m_hWnd, m_selectedSlot); }
void CHwsCaptureMainDlg::OnCropAdd() { HWS_VIDEO_CROP_RECT rc = {0,0,640,480}; CaptureManager::Slot* s = m_mgr.GetSlot(m_selectedSlot); if (s && s->cropRect.width > 0) rc = s->cropRect; if (ShowCropDialog(rc)) m_mgr.SetCrop(m_selectedSlot, rc); }
void CHwsCaptureMainDlg::OnCropRemove() { m_mgr.RemoveCrop(m_selectedSlot); }
void CHwsCaptureMainDlg::OnAudioMonitorOn() { if (!m_mgr.EnableAudioMonitor(m_selectedSlot)) AfxMessageBox(L"Please select audio source first.", MB_ICONINFORMATION); }
void CHwsCaptureMainDlg::OnAudioMonitorOff() { m_mgr.DisableAudioMonitor(m_selectedSlot); }
void CHwsCaptureMainDlg::OnExitApp() { SafeShutdown(); EndDialog(IDOK); }
void CHwsCaptureMainDlg::OnContextVideoSource() { OnSourceVideo(); }
void CHwsCaptureMainDlg::OnContextAudioSource() { OnSourceAudio(); }
void CHwsCaptureMainDlg::OnContextAudioMonitorOn() { OnAudioMonitorOn(); }
void CHwsCaptureMainDlg::OnContextAudioMonitorOff() { OnAudioMonitorOff(); }

void CHwsCaptureMainDlg::OnContextRemoveVideo()
{
    m_mgr.StopSlotAndRepairPeers(m_selectedSlot);
    if (m_selectedSlot >= 0 && m_selectedSlot < (int)m_panes.size()) {
        CPreviewPane* pane = m_panes[(size_t)m_selectedSlot];
        if (pane) {
            pane->m_active = false;
            pane->Invalidate(FALSE);
        }
    }
    UpdateTitle();
}
