自作Windowsランチャー

```
// Launcher.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "framework.h"
#include "Launcher.h"
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <shellapi.h>
#include <windows.h>
#include <shlobj.h>  // SHGetKnownFolderPath
#include <iostream>
#include <fileapi.h>

#define MAX_LOADSTRING 100
#define HOTKEY_ID 1  // ホットキーのID
#define CANDIDATE_MAX 500

// グローバル変数
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名
WNDPROC origEditProc = NULL;
WNDPROC origEditProc2 = NULL;
HWND hWnd;
HWND edittext;
HWND hListBox;                                  // 候補表示用のリストボックス
int candidateCount = 0;                         // 候補数
int selIndex;
std::string fname = "C:\\launcher\\launcherconfig.ini";
std::wstring Ckey[CANDIDATE_MAX];
std::wstring Cvalue[CANDIDATE_MAX];
int keynum = 2;                                 // ショートカットキーナンバー
ULONGLONG lastModifiedTime = 0;

// 関数宣言
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK    SubclassProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK    SubclassProc2(HWND, UINT, WPARAM, LPARAM);
void                LoadCandidatesFromFile(const std::string& filename);
std::wstring        Utf8ToWstring(const std::string& utf8Str);
std::string         RemoveUTF8BOM(const std::string& str);
bool                WaitForSystemReady(int maxWaitTime = 10000, int interval = 500);
ULONGLONG           FileTimeToULL(const FILETIME& ft);
bool                GetFileLastWriteTime(const std::wstring& filepath, ULONGLONG& outTime);


// エントリポイント
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR lpCmdLine,
                      _In_ int nCmdShow)
{
    // 多重起動防止
    HANDLE hMutex = CreateMutexW(NULL, FALSE, L"LauncherAppUniqueMutex");
    if (hMutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
        MessageBoxW(NULL, L"すでに起動しています。", L"多重起動防止", MB_OK | MB_ICONEXCLAMATION);
        return 0;
    }

    if (!WaitForSystemReady()) {
        MessageBoxW(nullptr, L"ファイルを開けませんでした", L"エラー", MB_OK);
    }

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    LoadCandidatesFromFile(fname);

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_LAUNCHER, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance(hInstance, nCmdShow)) {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_LAUNCHER));
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0)) {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

// システム準備完了待ち
bool WaitForSystemReady(int maxWaitTime, int interval) {
    int elapsedTime = 0;
    while (elapsedTime < maxWaitTime) {
        PWSTR path = nullptr;
        if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &path))) {
            CoTaskMemFree(path);
            return true;
        }
        Sleep(interval);
        elapsedTime += interval;
    }
    return false;
}

// FILETIME → ULONGLONG 変換
ULONGLONG FileTimeToULL(const FILETIME& ft) {
    ULARGE_INTEGER uli;
    uli.LowPart = ft.dwLowDateTime;
    uli.HighPart = ft.dwHighDateTime;
    return uli.QuadPart;
}

// ファイルの最終更新時刻取得
bool GetFileLastWriteTime(const std::wstring& filepath, ULONGLONG& outTime) {
    HANDLE hFile = CreateFileW(
        filepath.c_str(),
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE) {
        return false;
    }
    FILETIME createTime, accessTime, writeTime;
    if (!GetFileTime(hFile, &createTime, &accessTime, &writeTime)) {
        CloseHandle(hFile);
        return false;
    }
    outTime = FileTimeToULL(writeTime);
    CloseHandle(hFile);
    return true;
}


// ウィンドウクラス登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_LAUNCHER));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_LAUNCHER);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}

// インスタンス初期化
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;
    hWnd = CreateWindowW(szWindowClass, szTitle, WS_POPUP | WS_BORDER,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd) {
        return FALSE;
    }
    SetWindowPos(hWnd, HWND_TOP, 0, 0, 175, 25, SWP_NOZORDER);
    SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);

    edittext = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
        0, 0, 175, 25, hWnd, (HMENU)2, NULL, NULL);
    hListBox = CreateWindowW(L"LISTBOX", NULL, WS_CHILD | WS_VISIBLE | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL,
        0, 30, 175, 75, hWnd, (HMENU)4, NULL, NULL);

    origEditProc = (WNDPROC)GetWindowLongPtr(hListBox, GWLP_WNDPROC);
    SetWindowLongPtr(hListBox, GWLP_WNDPROC, (LONG_PTR)SubclassProc);
    origEditProc2 = (WNDPROC)GetWindowLongPtr(edittext, GWLP_WNDPROC);
    SetWindowLongPtr(edittext, GWLP_WNDPROC, (LONG_PTR)SubclassProc2);

    if (keynum == 1) RegisterHotKey(hWnd, HOTKEY_ID, MOD_CONTROL | MOD_ALT, 0x54);
    if (keynum == 2) RegisterHotKey(hWnd, HOTKEY_ID, 0, VK_NONCONVERT);

    ShowWindow(hListBox, SW_HIDE);
    ShowWindow(edittext, SW_SHOW);
    ShowWindow(hWnd, SW_HIDE);
    SetFocus(edittext);
    UpdateWindow(hWnd);
    return TRUE;
}

// メインウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_HOTKEY:
        if (!IsWindowVisible(hWnd)) {
            SetMenu(hWnd, nullptr);
            SetWindowLong(hWnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
            ULONGLONG currentModifiedTime;
            if (GetFileLastWriteTime(Utf8ToWstring(fname), currentModifiedTime)) {
                if (currentModifiedTime != lastModifiedTime) {
                    LoadCandidatesFromFile(fname);
                    lastModifiedTime = currentModifiedTime;
                }
            }
            SetForegroundWindow(hWnd);
            SetActiveWindow(hWnd);
            ShowWindow(hWnd, SW_SHOW);
            SendMessageW(hListBox, LB_RESETCONTENT, 0, 0);
            SetWindowText(edittext, L"");
            SetFocus(edittext);
        }
        else {
            ShowWindow(hWnd, SW_HIDE);
        }
        return 0;

    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case 2: // テキストボックスが入力された
            if (HIWORD(wParam) == EN_CHANGE) {
                wchar_t inputText[256];
                GetWindowTextW(edittext, inputText, 256);
                SendMessageW(hListBox, LB_RESETCONTENT, 0, 0);
                candidateCount = 0;
                for (int i = 0; i < CANDIDATE_MAX; i++) {
                    if (Ckey[i].empty()) break;
                    if (wcslen(inputText) > 0 && Ckey[i].find(inputText) == 0) {
                        SendMessageW(hListBox, LB_ADDSTRING, 0, (LPARAM)Ckey[i].c_str());
                        candidateCount++;
                    }
                }
                if (candidateCount > 0) {
                    ShowWindow(hListBox, SW_SHOW);
                    SetFocus(hListBox);
                    SendMessageW(hListBox, LB_SETCURSEL, 0, 0);
                    SetWindowPos(hWnd, HWND_TOP, 0, 0, 175, 110, SWP_NOZORDER);
                }
                else {
                    SendMessageW(hListBox, LB_RESETCONTENT, 0, 0);
                    ShowWindow(hListBox, SW_HIDE);
                    SetFocus(edittext);
                    SetWindowPos(hWnd, HWND_TOP, 0, 0, 175, 25, SWP_NOZORDER);
                }
            }
            break;
        case 4: // リストボックスが選択された
            if (HIWORD(wParam) == LBN_SELCHANGE) {
                selIndex = (int)SendMessageW(hListBox, LB_GETCURSEL, 0, 0);
                if (selIndex != LB_ERR) {
                    wchar_t selectedText[256] = { 0 };
                    SendMessageW(hListBox, LB_GETTEXT, selIndex, (LPARAM)selectedText);
                }
            }
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    }
    case WM_KEYDOWN:
    {
        HWND focusedWnd = GetFocus();
        if (focusedWnd == hListBox) {
            if (!(wParam == VK_DOWN || wParam == VK_UP || wParam == VK_RETURN)) {
                BYTE keyboardState[256];
                if (GetKeyboardState(keyboardState)) {
                    WCHAR unicodeChar;
                    UINT scanCode = MapVirtualKey((UINT)wParam, MAPVK_VK_TO_VSC);
                    if (ToUnicode((UINT)wParam, scanCode, keyboardState, &unicodeChar, 1, 0) == 1) {
                        SendMessage(edittext, WM_CHAR, (WPARAM)unicodeChar, 0);
                    }
                }
            }
            if (wParam == VK_RETURN) {
                selIndex = (int)SendMessageW(hListBox, LB_GETCURSEL, 0, 0);
                if (selIndex != LB_ERR) {
                    wchar_t selectedText[256] = { 0 };
                    SendMessageW(hListBox, LB_GETTEXT, selIndex, (LPARAM)selectedText);
                    for (int i = 0; i < CANDIDATE_MAX; i++) {
                        if (Ckey[i] == selectedText) {
                            ShellExecuteW(NULL, L"open", Cvalue[i].c_str(), NULL, NULL, SW_SHOWNORMAL);
                            ShowWindow(hWnd, SW_HIDE);
                            break;
                        }
                    }
                }
            }
        }
        break;
    }
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// サブクラスプロシージャ（リストボックス）
LRESULT CALLBACK SubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HWND parentWnd = GetParent(hwnd);
    if (msg == WM_KEYDOWN) {
        LRESULT result = CallWindowProc(origEditProc, hwnd, msg, wParam, lParam);
        SendMessage(parentWnd, WM_KEYDOWN, wParam, lParam);
        return result;
    }
    return CallWindowProc(origEditProc, hwnd, msg, wParam, lParam);
}

// サブクラスプロシージャ（テキストボックス）
LRESULT CALLBACK SubclassProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HWND parentWnd = GetParent(hwnd);
    if (msg == WM_KEYDOWN) {
        LRESULT result = CallWindowProc(origEditProc2, hwnd, msg, wParam, lParam);
        SendMessage(parentWnd, WM_KEYDOWN, wParam, lParam);
        return result;
    }
    return CallWindowProc(origEditProc2, hwnd, msg, wParam, lParam);
}

// 変換関数: UTF-8 → wstring
std::wstring Utf8ToWstring(const std::string& utf8Str) {
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
    if (size_needed <= 0) return L"";
    std::wstring wstr(size_needed - 1, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &wstr[0], size_needed);
    return wstr;
}

// UTF-8 BOM (Byte Order Mark) の削除
std::string RemoveUTF8BOM(const std::string& str) {
    if (str.size() >= 3 &&
        (unsigned char)str[0] == 0xEF &&
        (unsigned char)str[1] == 0xBB &&
        (unsigned char)str[2] == 0xBF) {
        return str.substr(3);
    }
    return str;
}

// 候補リストファイル読み込み
void LoadCandidatesFromFile(const std::string& filename) {
    for (int i = 0; i < CANDIDATE_MAX; i++) {
        Ckey[i].clear();
        Cvalue[i].clear();
    }
    int count = 0;
    std::ifstream file(filename, std::ios::binary);
    if (!file) {
        MessageBoxW(nullptr, L"ファイルを開けませんでした", L"エラー", MB_OK);
        return;
    }
    std::string line;
    while (std::getline(file, line)) {
        if (count == 0) {
            line = RemoveUTF8BOM(line);
        }
        if (line.empty()) continue;
        if (!line.empty() && line.back() == '\r') {
            line.pop_back();
        }
        if (line[0] == '#') continue;
        size_t pos = line.find(':');
        if (pos == std::string::npos) continue;
        std::string key = line.substr(0, pos);
        std::string value = line.substr(pos + 1);
        if (key.empty() || value.empty()) continue;
        if (key._Equal("shortkey")) {
            keynum = atoi(value.c_str());
            continue;
        }
        Ckey[count] = Utf8ToWstring(key);
        Cvalue[count] = Utf8ToWstring(value);
        count++;
        if (count >= CANDIDATE_MAX) break;
    }
    file.close();
}
```