← 컨트롤러로

C++ 연동 가이드

모바일 토글(ON/OFF)에 따라 PC의 모든 모니터를 검정으로 덮거나 복구하는 Windows 클라이언트.

1. 동작 개요

① 페어링 — 모바일에서 6자리 코드 발급 → C++ 첫 실행 때 입력 → 서버가 API 키를 내려주고 자동 저장(blackout_key.txt).
② 폴링 — 이후 10초마다 /api/state.php?key=... 호출 → 본문이 "1"(정상) 또는 "0"(검정).
③ 블랙아웃0이면 가상 데스크톱 전체(=4패널 모두)를 덮는 검정 창 표시, 1이면 제거.

2. API 엔드포인트

페어링 코드 → 키 교환 (최초 1회)
GET https://m.tion.kr/api/pair_claim.php?code=123456
{"ok":true,"key":"mt_xxxxxxxx"}
상태 조회 (10초마다, C++ 권장)
GET https://m.tion.kr/api/state.php?key=mt_xxxx
→ 본문 1(정상) 또는 0(검정) · 한 글자만 보면 됨
상세(JSON)가 필요하면
GET https://m.tion.kr/api/state.php?key=mt_xxxx&format=json
{"ok":true,"screen":0,"black":true,"auto_revert_in":540,"ts":...}
screen의미C++ 동작
1화면 정상(켜짐)오버레이 제거
0검정(블랙아웃)전 모니터 검정 덮기

※ 타이머 자동복귀는 서버가 알아서 처리합니다. C++는 그냥 10초마다 1/0만 읽으면 됩니다.

3. 전체 소스 (단일 파일, 복붙)

// blackout.cpp — m.tion.kr 모니터 블랙아웃 클라이언트 (Windows)
// 빌드(MSVC):  cl /EHsc blackout.cpp winhttp.lib user32.lib gdi32.lib
// 빌드(MinGW): g++ blackout.cpp -o blackout.exe -lwinhttp -lgdi32 -luser32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winhttp.h>
#include <string>
#include <fstream>
#include <iostream>
#pragma comment(lib, "winhttp.lib")

static const wchar_t* HOST = L"m.tion.kr";   // 서버 주소

// ───── HTTPS GET → 응답 본문 반환 ─────
std::string httpGet(const std::wstring& path) {
    std::string out;
    HINTERNET hS = WinHttpOpen(L"blackout/1.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hS) return out;
    HINTERNET hC = WinHttpConnect(hS, HOST, INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (hC) {
        HINTERNET hR = WinHttpOpenRequest(hC, L"GET", path.c_str(), NULL,
            WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
        if (hR) {
            if (WinHttpSendRequest(hR, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                    WINHTTP_NO_REQUEST_DATA, 0, 0, 0)
                && WinHttpReceiveResponse(hR, NULL)) {
                DWORD avail = 0;
                do {
                    avail = 0; WinHttpQueryDataAvailable(hR, &avail);
                    if (!avail) break;
                    char buf[2048]; DWORD got = 0;
                    DWORD toRead = avail < sizeof(buf) ? avail : sizeof(buf);
                    if (WinHttpReadData(hR, buf, toRead, &got) && got) out.append(buf, got);
                } while (avail > 0);
            }
            WinHttpCloseHandle(hR);
        }
        WinHttpCloseHandle(hC);
    }
    WinHttpCloseHandle(hS);
    return out;
}

// ───── 키 저장/로드 (exe 옆 blackout_key.txt) ─────
std::string keyPath() {
    char b[MAX_PATH]; GetModuleFileNameA(NULL, b, MAX_PATH);
    std::string p(b); auto s = p.find_last_of("\\/");
    return (s == std::string::npos ? "" : p.substr(0, s + 1)) + "blackout_key.txt";
}
std::string loadKey() { std::ifstream f(keyPath()); std::string k; std::getline(f, k); return k; }
void saveKey(const std::string& k) { std::ofstream f(keyPath()); f << k; }

// JSON에서 "field":"값" 추출 (라이브러리 없이)
std::string field(const std::string& j, const std::string& name) {
    std::string pat = "\"" + name + "\":\""; auto p = j.find(pat);
    if (p == std::string::npos) return "";
    p += pat.size(); auto e = j.find('"', p);
    return e == std::string::npos ? "" : j.substr(p, e - p);
}

// ───── 6자리 페어링 코드 → API 키 ─────
std::string pairWithCode() {
    std::cout << "\n[연결] m.tion.kr 모바일에서 '연결 코드 만들기'를 누르세요.\n";
    std::cout << "6자리 코드 입력: ";
    std::string in; std::getline(std::cin, in);
    std::string d; for (char c : in) if (c >= '0' && c <= '9') d += c;
    std::wstring wd(d.begin(), d.end());
    std::string body = httpGet(L"/api/pair_claim.php?code=" + wd);
    std::string key = field(body, "key");
    if (!key.empty()) { saveKey(key); std::cout << "연결 성공! 키 저장됨.\n"; }
    else std::cout << "코드 만료/오류. 새 코드로 다시 시도하세요.\n";
    return key;
}

// ───── 블랙아웃 오버레이: 모든 모니터(가상 데스크톱 전체)를 덮는 검정 창 ─────
HWND g_ov = NULL;
LRESULT CALLBACK WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
    if (m == WM_PAINT) {
        PAINTSTRUCT ps; HDC dc = BeginPaint(h, &ps); RECT r; GetClientRect(h, &r);
        FillRect(dc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); EndPaint(h, &ps); return 0;
    }
    return DefWindowProc(h, m, w, l);
}
void showBlack() {
    if (g_ov) return;
    static bool reg = false;
    if (!reg) {
        WNDCLASSA wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = GetModuleHandle(NULL);
        wc.lpszClassName = "BlackoutWnd"; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClassA(&wc); reg = true;
    }
    int x = GetSystemMetrics(SM_XVIRTUALSCREEN), y = GetSystemMetrics(SM_YVIRTUALSCREEN);
    int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN), cy = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    g_ov = CreateWindowExA(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, "BlackoutWnd", "",
        WS_POPUP, x, y, cx, cy, NULL, NULL, GetModuleHandle(NULL), NULL);
    ShowWindow(g_ov, SW_SHOW);
    SetWindowPos(g_ov, HWND_TOPMOST, x, y, cx, cy, SWP_SHOWWINDOW);
}
void hideBlack() { if (g_ov) { DestroyWindow(g_ov); g_ov = NULL; } }

int main() {
    std::string key = loadKey();
    while (key.empty()) key = pairWithCode();   // 최초 1회만 코드 입력

    std::wstring path = L"/api/state.php?key=" + std::wstring(key.begin(), key.end());
    std::cout << "감시 시작 (10초 주기). 이 프로그램을 켜두면 자동으로 동작합니다.\n";
    bool black = false;

    for (;;) {
        std::string s = httpGet(path);   // 응답: "1"(정상) 또는 "0"(검정)
        if (!s.empty()) {
            bool wantBlack = (s[0] == '0');
            if (wantBlack && !black) { showBlack(); black = true; }
            else if (!wantBlack && black) { hideBlack(); black = false; }
        }
        if (black && g_ov) SetWindowPos(g_ov, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE);

        // 10초 대기하면서 창 메시지 처리
        for (int i = 0; i < 20; i++) {
            MSG msg;
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
            Sleep(500);
        }
    }
    return 0;
}

4. 빌드

MSVC (Visual Studio 개발자 명령 프롬프트)

cl /EHsc blackout.cpp winhttp.lib user32.lib gdi32.lib

MinGW (g++)

g++ blackout.cpp -o blackout.exe -lwinhttp -lgdi32 -luser32

5. 실행 & 자동 시작

처음 한 번은 콘솔에서 실행해 6자리 코드를 입력합니다(키가 blackout_key.txt에 저장됨).
이후엔 키가 저장돼 있으므로 코드 입력 없이 바로 감시합니다. 시작프로그램에 등록(예: shell:startup 폴더에 바로가기)하면 부팅 시 자동 동작.
콘솔 없이 백그라운드로 돌리려면 키 저장 후, 창 숨김 빌드(MinGW -mwindows) 또는 작업 스케줄러 "사용자 로그온 시 / 숨김"으로 등록하세요.

6. 참고