Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build GPU-Switcher

on:
push:
branches: [ "main" ]
branches: [ "main", "feature/**" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
77 changes: 74 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<UINT>(ARRAYSIZE(featureLevels)),
D3D11_SDK_VERSION,
g_device.GetAddressOf(), &level, g_context.GetAddressOf());
Expand Down Expand Up @@ -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);
Expand All @@ -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();

Expand All @@ -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;
Expand Down
34 changes: 34 additions & 0 deletions src/resource.rc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <winres.h>
#include "resource.h"

IDI_APP_ICON ICON "icons/ico/main-rainbow.ico"
Expand All @@ -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
Loading