diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a54b77a..024c474 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build GPU-Switcher on: push: - branches: [ "main" ] + branches: [ "main", "feature/**" ] pull_request: branches: [ "main" ] workflow_dispatch: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4bbd811 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release GPU-Switcher + +on: + push: + tags: + - "v*" + +jobs: + release-windows: + runs-on: windows-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4.2.2 + + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: x64 + + - name: Configure CMake + run: cmake -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --config Release + + - name: Package Release + shell: pwsh + run: | + $tag = "${{ github.ref_name }}" + $zipName = "GPU-Switcher-${tag}-windows-x64.zip" + Compress-Archive -Path "build/GPU-Switcher.exe", "README.md", "LICENSE" -DestinationPath $zipName + $hash = (Get-FileHash -Algorithm SHA256 $zipName).Hash + "$hash $zipName" | Out-File -Encoding ascii -FilePath "${zipName}.sha256" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: | + GPU-Switcher-*-windows-x64.zip + GPU-Switcher-*-windows-x64.zip.sha256 + draft: false + prerelease: false + generate_release_notes: true diff --git a/README.md b/README.md index e9251dd..434bbbf 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,16 @@ After this, GPU‑Switcher will consistently trigger a dGPU display switch on la --- +## Command‑line options + +| Option | Description | +|---|---| +| *(none)* | Launches the tray utility. If an instance is already running, highlights the active instance in the tray. | +| `--exit` / `--quit` | Gracefully closes the running GPU-Switcher instance. | +| `--help` / `-h` | Shows usage instructions and available flags. | + +--- + ## How it works On startup, GPU‑Switcher: diff --git a/src/main.cpp b/src/main.cpp index 30702ef..c42022d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ extern "C" { #define WM_TRAY (WM_USER + 1) #define WM_RESET_DONE (WM_USER + 2) +#define WM_ACTIVATE_INSTANCE (WM_USER + 3) #define TRAY_ID 1 #define ID_START_WINDOWS 1001 #define ID_RESET_DISPLAYS 1002 @@ -129,7 +130,7 @@ static bool AcquireDGpu() HRESULT hr = D3D11CreateDevice( bestAdapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, - nullptr, 0, + nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, static_cast(ARRAYSIZE(featureLevels)), D3D11_SDK_VERSION, g_device.GetAddressOf(), &level, g_context.GetAddressOf()); @@ -454,6 +455,33 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } return 0; + case WM_ACTIVATE_INSTANCE: + { + UpdateTrayStatus(); + NOTIFYICONDATAW balloon = g_nid; + balloon.uFlags |= NIF_INFO; + balloon.dwInfoFlags = (g_dGpuActive ? NIIF_INFO : NIIF_WARNING) | NIIF_NOSOUND; + wcsncpy_s(balloon.szInfoTitle, L"GPU-Switcher", _TRUNCATE); + if (g_resetInProgress) + { + wcsncpy_s(balloon.szInfo, L"Already running (display reset in progress)", _TRUNCATE); + } + else if (g_dGpuActive) + { + swprintf_s(balloon.szInfo, L"Already active in system tray.\nGPU: %s", g_activeGpuName.c_str()); + } + else + { + wcsncpy_s(balloon.szInfo, L"Already running (dGPU inactive)", _TRUNCATE); + } + Shell_NotifyIconW(NIM_MODIFY, &balloon); + return 0; + } + + case WM_CLOSE: + PostQuitMessage(0); + return 0; + case WM_DESTROY: KillTimer(hwnd, TIMER_RESUME_REACQUIRE); KillTimer(hwnd, TIMER_HEALTH_CHECK); @@ -477,18 +505,56 @@ static int RunElevatedReset() // Removing the parameter name silences MSVC warning C4100. int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int) { - // Check for elevated reset mode first + // Check command line arguments first int argc = 0; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); bool resetMode = false; + bool exitMode = false; + bool helpMode = false; + if (argv) { for (int i = 1; i < argc; ++i) + { if (_wcsicmp(argv[i], L"--reset-gpu") == 0) resetMode = true; + else if (_wcsicmp(argv[i], L"--exit") == 0 || _wcsicmp(argv[i], L"--quit") == 0) + exitMode = true; + else if (_wcsicmp(argv[i], L"--help") == 0 || _wcsicmp(argv[i], L"-h") == 0 || _wcsicmp(argv[i], L"/?") == 0) + helpMode = true; + } LocalFree(argv); } + if (helpMode) + { + MessageBoxW(nullptr, + L"GPU-Switcher v" APP_VERSION L"\n\n" + L"Usage:\n" + L" GPU-Switcher.exe Start tray application\n" + L" GPU-Switcher.exe --exit Gracefully close running instance\n" + L" GPU-Switcher.exe --help Show this help dialog\n\n" + L"Helper Flags:\n" + L" --reset-gpu Cycle display adapters (elevated helper)", + L"GPU-Switcher", MB_OK | MB_ICONINFORMATION); + return 0; + } + + if (exitMode) + { + HWND hExisting = FindWindowW(L"TrayHookClass", L"GPU-Switcher"); + if (hExisting) + { + PostMessageW(hExisting, WM_CLOSE, 0, 0); + LogInfo(L"Sent exit request to running GPU-Switcher instance"); + } + else + { + LogInfo(L"No running GPU-Switcher instance found to exit"); + } + return 0; + } + if (resetMode) return RunElevatedReset(); @@ -504,7 +570,12 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int) if (GetLastError() == ERROR_ALREADY_EXISTS) { - LogInfo(L"Another instance is already running"); + LogInfo(L"Another instance is already running — notifying running instance"); + HWND hExisting = FindWindowW(L"TrayHookClass", L"GPU-Switcher"); + if (hExisting) + { + PostMessageW(hExisting, WM_ACTIVATE_INSTANCE, 0, 0); + } CloseHandle(g_hMutex); g_hMutex = nullptr; return 0; diff --git a/src/resource.rc b/src/resource.rc index 12ab32c..59c88ba 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -1,3 +1,4 @@ +#include #include "resource.h" IDI_APP_ICON ICON "icons/ico/main-rainbow.ico" @@ -7,3 +8,36 @@ IDI_ICON_NVIDIA ICON "icons/ico/nvidia-green.ico" IDI_ICON_AMD ICON "icons/ico/amd-red.ico" IDI_ICON_UNKNOWN ICON "icons/ico/unknown-grey.ico" IDI_ICON_WARNING ICON "icons/ico/warning-yellow.ico" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,1,0,0 +PRODUCTVERSION 1,1,0,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Tiflit" + VALUE "FileDescription", "GPU-Switcher - Discrete GPU Activator" + VALUE "FileVersion", "1.1.0.0" + VALUE "InternalName", "GPU-Switcher" + VALUE "LegalCopyright", "Copyright (C) 2026" + VALUE "OriginalFilename", "GPU-Switcher.exe" + VALUE "ProductName", "GPU-Switcher" + VALUE "ProductVersion", "1.1.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END