Merge pull request #1277 from quantum5/idd-no-gpu-warn
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled

[idd] helper: implement warning when there is no GPU
This commit is contained in:
Geoffrey McRae
2026-06-03 21:39:00 +10:00
committed by GitHub
12 changed files with 313 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "CCheckbox.h"
#include <windowsx.h>
#include <CDebug.h>
CCheckbox::CCheckbox(LPCWSTR title, DWORD style, HWND parent) :
CButton(title, style | BS_CHECKBOX, parent)
{
}
bool CCheckbox::isChecked()
{
return Button_GetCheck(m_hwnd) == BST_CHECKED;
}
void CCheckbox::setChecked(bool checked)
{
Button_SetCheck(m_hwnd, checked ? BST_CHECKED : BST_UNCHECKED);
}

View File

@@ -0,0 +1,31 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "CButton.h"
class CCheckbox : public CButton
{
public:
CCheckbox(LPCWSTR title, DWORD style, HWND parent);
bool isChecked();
void setChecked(bool checked);
};

View File

@@ -23,6 +23,7 @@
#include "CGroupBox.h"
#include "CEditWidget.h"
#include "CButton.h"
#include "CCheckbox.h"
#include <CDebug.h>
#include <windowsx.h>
#include <strsafe.h>
@@ -51,6 +52,7 @@ CConfigWindow::CConfigWindow() : m_scale(1)
{
m_modes = m_settings.getModes();
m_defaultRefresh = m_settings.getDefaultRefresh();
m_noGPU = m_settings.getNoGPU();
}
if (!CreateWindowEx(0, MAKEINTATOM(s_atom), L"Looking Glass IDD Configuration",
@@ -90,6 +92,7 @@ void CConfigWindow::updateFont()
*m_version, *m_modeGroup, *m_modeBox, *m_widthLabel, *m_heightLabel, *m_refreshLabel,
*m_modeWidth, *m_modeHeight, *m_modeRefresh, *m_modeUpdate, *m_modeDelete, *m_modeReset,
*m_autosizeGroup, *m_defRefreshLabel, *m_defRefresh, *m_defRefreshHz,
*m_prefGroup, *m_prefNoGPU,
}))
SendMessage(child, WM_SETFONT, (WPARAM)m_font.Get(), 1);
}
@@ -168,6 +171,12 @@ LRESULT CConfigWindow::onCreate()
else
m_defRefresh->disable();
m_prefGroup.reset(new CGroupBox(L"Preferences", WS_CHILD | WS_VISIBLE, m_hwnd));
m_prefNoGPU.reset(new CCheckbox(L"Disable no GPU warning", WS_CHILD | WS_VISIBLE, m_hwnd));
if (m_noGPU)
m_prefNoGPU->setChecked(*m_noGPU);
LONG width, height;
getMinimumSize(width, height);
SetWindowPos(m_hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER);
@@ -205,6 +214,9 @@ LRESULT CConfigWindow::onResize(DWORD width, DWORD height)
pos.pinTopLeft(*m_defRefreshLabel, 236, 64, 95, 20);
pos.pinTopLeft(*m_defRefresh, 331, 64, 63, 20);
pos.pinTopLeft(*m_defRefreshHz, 398, 64, 16, 20);
pos.pinTopLeft(*m_prefGroup, 224, 100, 200, 52);
pos.pinTopLeft(*m_prefNoGPU, 236, 124, 176, 20);
return 0;
}
@@ -308,5 +320,12 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd)
if (result != ERROR_SUCCESS)
DEBUG_ERROR_HR((HRESULT)result, "Failed to default refresh");
}
else if (m_prefNoGPU && hwnd == *m_prefNoGPU && code == BN_CLICKED && m_noGPU)
{
DEBUG_INFO("GPU button clicked");
*m_noGPU ^= true;
m_settings.setNoGPU(*m_noGPU);
m_prefNoGPU->setChecked(*m_noGPU);
}
return 0;
}

View File

@@ -32,6 +32,7 @@ class CListBox;
class CGroupBox;
class CEditWidget;
class CButton;
class CCheckbox;
class CConfigWindow : public CWindow
{
@@ -58,12 +59,16 @@ class CConfigWindow : public CWindow
std::unique_ptr<CEditWidget> m_defRefresh;
std::unique_ptr<CStaticWidget> m_defRefreshHz;
std::unique_ptr<CGroupBox> m_prefGroup;
std::unique_ptr<CCheckbox> m_prefNoGPU;
std::function<void()> m_onDestroy;
double m_scale;
Microsoft::WRL::Wrappers::HandleT<FontTraits> m_font;
CRegistrySettings m_settings;
std::optional<std::vector<DisplayMode>> m_modes;
std::optional<DWORD> m_defaultRefresh;
std::optional<bool> m_noGPU;
void getMinimumSize(LONG &width, LONG &height);
void updateFont();

View File

@@ -20,6 +20,7 @@
#include "CNotifyWindow.h"
#include "CConfigWindow.h"
#include "Devices.h"
#include <CDebug.h>
#include <windowsx.h>
#include <strsafe.h>
@@ -161,6 +162,42 @@ void CNotifyWindow::registerIcon()
if (!Shell_NotifyIcon(NIM_SETVERSION, &m_iconData))
DEBUG_ERROR_HR(GetLastError(), "Shell_NotifyIcon(NIM_SETVERSION)");
bool hasGPU;
if (!checkGPU(hasGPU))
DEBUG_ERROR("Failed to check if the system has a GPU");
else if (hasGPU)
DEBUG_INFO("GPU identified");
else
{
DEBUG_INFO("System has no GPU");
noGPUNotification();
}
}
void CNotifyWindow::noGPUNotification()
{
CRegistrySettings settings;
LSTATUS error = settings.open();
if (error != ERROR_SUCCESS)
DEBUG_ERROR_HR(error, "Failed to load settings");
auto noGPU = settings.getNoGPU();
if (noGPU.has_value() && noGPU.value())
return;
NOTIFYICONDATA nid;
memcpy(&nid, &m_iconData, sizeof nid);
nid.uFlags = NIF_INFO;
nid.dwInfoFlags = NIIF_WARNING;
StringCbCopy(nid.szInfoTitle, sizeof nid.szInfoTitle, L"No GPU found!");
StringCbCopy(nid.szInfo, sizeof nid.szInfo,
L"GPU acceleration will not be available. "
L"Looking Glass will use software rendering.");
if (!Shell_NotifyIcon(NIM_MODIFY, &nid))
DEBUG_ERROR_HR(GetLastError(), "Shell_NotifyIcon(NIM_MODIFY)");
}
HWND CNotifyWindow::hwndDialog()

View File

@@ -36,6 +36,7 @@ class CNotifyWindow : public CWindow
LRESULT onNotifyIcon(UINT uEvent, WORD wIconId, int x, int y);
void registerIcon();
void noGPUNotification();
virtual LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
virtual LRESULT onCreate() override;

View File

@@ -177,7 +177,7 @@ std::optional<DWORD> CRegistrySettings::getDefaultRefresh()
case ERROR_FILE_NOT_FOUND:
return DEFAULT_REFRESH;
default:
DEBUG_ERROR_HR(status, "RegGetValue(Modes) length computation");
DEBUG_ERROR_HR(status, "RegGetValue(Modes)");
return {};
}
}
@@ -186,3 +186,26 @@ LSTATUS CRegistrySettings::setDefaultRefresh(DWORD refresh)
{
return RegSetValueEx(hKey, L"DefaultRefresh", 0, REG_DWORD, (LPBYTE) &refresh, sizeof(DWORD));
}
std::optional<bool> CRegistrySettings::getNoGPU()
{
DWORD result, cbData = sizeof result;
LSTATUS status = RegGetValue(hKey, nullptr, L"NoGPU", RRF_RT_REG_DWORD, nullptr, &result, &cbData);
switch (status)
{
case ERROR_SUCCESS:
return !!result;
case ERROR_FILE_NOT_FOUND:
return false;
default:
DEBUG_ERROR_HR(status, "RegGetValue(NoGPU)");
return {};
}
}
LSTATUS CRegistrySettings::setNoGPU(bool noGPU)
{
DWORD dwValue = noGPU;
return RegSetValueEx(hKey, L"NoGPU", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD));
}

View File

@@ -50,4 +50,7 @@ public:
std::optional<DWORD> getDefaultRefresh();
LSTATUS setDefaultRefresh(DWORD refresh);
std::optional<bool> getNoGPU();
LSTATUS setNoGPU(bool noGPU);
};

106
idd/LGIddHelper/Devices.cpp Normal file
View File

@@ -0,0 +1,106 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Devices.h"
#include <windows.h>
#include <devguid.h>
#include <setupapi.h>
#include <CDebug.h>
inline static bool wprefix(const wchar_t *pre, const wchar_t *str)
{
return wcsncmp(pre, str, wcslen(pre)) == 0;
}
bool checkGPU(bool &found)
{
found = false;
HDEVINFO hDevInfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
DEBUG_WARN_HR(GetLastError(), L"SetupDiGetClassDevsW");
return false;
}
SP_DEVINFO_DATA devInfo = { 0 };
devInfo.cbSize = sizeof devInfo;
for (DWORD dwIndex = 0; SetupDiEnumDeviceInfo(hDevInfo, dwIndex, &devInfo); ++dwIndex)
{
DWORD dwSizeRequired;
DWORD dwPropertyType;
SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfo, SPDRP_HARDWAREID, &dwPropertyType, NULL, 0, &dwSizeRequired);
DWORD dwLastError = GetLastError();
if (dwLastError == ERROR_INVALID_DATA)
continue;
else if (dwLastError != ERROR_INSUFFICIENT_BUFFER)
{
DEBUG_WARN_HR(GetLastError(), L"SetupDiGetDeviceRegistryPropertyW(SPDRP_HARDWAREID) size calculation");
goto fail;
}
if (dwPropertyType != REG_MULTI_SZ)
{
DEBUG_WARN(L"SetupDiGetDeviceRegistryPropertyW(SPDRP_HARDWAREID) returned wrong type");
goto fail;
}
LPWSTR lpBuffer = (LPWSTR)malloc(dwSizeRequired);
if (!lpBuffer)
{
DEBUG_WARN(L"failed to allocate memory for SetupDiGetDeviceRegistryPropertyW(SPDRP_HARDWAREID)");
goto fail;
}
if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfo, SPDRP_HARDWAREID, &dwPropertyType, (PBYTE)lpBuffer, dwSizeRequired, NULL))
{
DEBUG_WARN_HR(GetLastError(), L"SetupDiGetDeviceRegistryPropertyW(SPDRP_HARDWAREID) for real");
free(lpBuffer);
goto fail;
}
for (LPWSTR lpHwId = lpBuffer; *lpHwId; lpHwId += wcslen(lpBuffer) + 1)
{
if (
wprefix(L"PCI\\VEN_10DE&", lpHwId) || // Nvidia
wprefix(L"PCI\\VEN_1002&", lpHwId) || // AMD
wprefix(L"PCI\\VEN_8086&", lpHwId) // Intel
)
{
found = true;
break;
}
}
free(lpBuffer);
if (found)
break;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return true;
fail:
SetupDiDestroyDeviceInfoList(hDevInfo);
return false;
}

23
idd/LGIddHelper/Devices.h Normal file
View File

@@ -0,0 +1,23 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
bool checkGPU(bool &found);

View File

@@ -180,6 +180,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd
<ItemGroup>
<ClCompile Include="$(SolutionDir)LGCommon\*.cpp" />
<ClCompile Include="CButton.cpp" />
<ClCompile Include="CCheckbox.cpp" />
<ClCompile Include="CConfigWindow.cpp" />
<ClCompile Include="CEditWidget.cpp" />
<ClCompile Include="CGroupBox.cpp" />
@@ -190,12 +191,14 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd
<ClCompile Include="CStaticWidget.cpp" />
<ClCompile Include="CWidget.cpp" />
<ClCompile Include="CWindow.cpp" />
<ClCompile Include="Devices.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="UIHelpers.cpp" />
</ItemGroup>
<ItemGroup>
<CLInclude Include="$(SolutionDir)LGCommon\*.h" />
<ClInclude Include="CButton.h" />
<ClInclude Include="CCheckbox.h" />
<ClInclude Include="CConfigWindow.h" />
<ClInclude Include="CEditWidget.h" />
<ClInclude Include="CGroupBox.h" />
@@ -205,6 +208,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd
<ClInclude Include="CRegistrySettings.h" />
<ClInclude Include="CStaticWidget.h" />
<ClInclude Include="CWidget.h" />
<ClInclude Include="Devices.h" />
<ClInclude Include="UIHelpers.h" />
<ClInclude Include="CWindow.h" />
</ItemGroup>

View File

@@ -53,6 +53,16 @@
<ClCompile Include="CGroupBox.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(SolutionDir)LGCommon\*.cpp" />
<ClCompile Include="CButton.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CEditWidget.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Devices.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<CLInclude Include="$(SolutionDir)LGCommon\*.h" />
@@ -94,6 +104,18 @@
<ClInclude Include="CGroupBox.h">
<Filter>Header Files</Filter>
</ClInclude>
<CLInclude Include="$(SolutionDir)LGCommon\*.h" />
<CLInclude Include="$(SolutionDir)LGCommon\*.h" />
<CLInclude Include="$(SolutionDir)LGCommon\*.h" />
<ClInclude Include="CButton.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CEditWidget.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Devices.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />