From 1a137f3c083e15e9d17f2fef4c9af61f73ad58d6 Mon Sep 17 00:00:00 2001 From: Quantum Date: Wed, 3 Jun 2026 00:27:29 -0400 Subject: [PATCH 1/5] [idd] helper: detect if system has GPU --- idd/LGIddHelper/CNotifyWindow.cpp | 9 ++ idd/LGIddHelper/Devices.cpp | 106 ++++++++++++++++++++ idd/LGIddHelper/Devices.h | 23 +++++ idd/LGIddHelper/LGIddHelper.vcxproj | 2 + idd/LGIddHelper/LGIddHelper.vcxproj.filters | 22 ++++ 5 files changed, 162 insertions(+) create mode 100644 idd/LGIddHelper/Devices.cpp create mode 100644 idd/LGIddHelper/Devices.h diff --git a/idd/LGIddHelper/CNotifyWindow.cpp b/idd/LGIddHelper/CNotifyWindow.cpp index 118cbf4c..34b90526 100644 --- a/idd/LGIddHelper/CNotifyWindow.cpp +++ b/idd/LGIddHelper/CNotifyWindow.cpp @@ -20,6 +20,7 @@ #include "CNotifyWindow.h" #include "CConfigWindow.h" +#include "Devices.h" #include #include #include @@ -161,6 +162,14 @@ 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"); } HWND CNotifyWindow::hwndDialog() diff --git a/idd/LGIddHelper/Devices.cpp b/idd/LGIddHelper/Devices.cpp new file mode 100644 index 00000000..f6c9f39d --- /dev/null +++ b/idd/LGIddHelper/Devices.cpp @@ -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 +#include +#include +#include + +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; +} diff --git a/idd/LGIddHelper/Devices.h b/idd/LGIddHelper/Devices.h new file mode 100644 index 00000000..2951be2e --- /dev/null +++ b/idd/LGIddHelper/Devices.h @@ -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); diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj b/idd/LGIddHelper/LGIddHelper.vcxproj index 08c39fce..5bdb657b 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj +++ b/idd/LGIddHelper/LGIddHelper.vcxproj @@ -190,6 +190,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + @@ -205,6 +206,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj.filters b/idd/LGIddHelper/LGIddHelper.vcxproj.filters index f9146e12..97b59054 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj.filters +++ b/idd/LGIddHelper/LGIddHelper.vcxproj.filters @@ -53,6 +53,16 @@ Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -94,6 +104,18 @@ Header Files + + + + + Header Files + + + Header Files + + + Header Files + From ebeb9d028e86cd09ad32b90a8c50cfd577af1a6c Mon Sep 17 00:00:00 2001 From: Quantum Date: Wed, 3 Jun 2026 00:47:40 -0400 Subject: [PATCH 2/5] [idd] helper/registry: load and store no GPU preference --- idd/LGIddHelper/CRegistrySettings.cpp | 25 ++++++++++++++++++++++++- idd/LGIddHelper/CRegistrySettings.h | 3 +++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/idd/LGIddHelper/CRegistrySettings.cpp b/idd/LGIddHelper/CRegistrySettings.cpp index 8e38ed4d..0742bf08 100644 --- a/idd/LGIddHelper/CRegistrySettings.cpp +++ b/idd/LGIddHelper/CRegistrySettings.cpp @@ -171,7 +171,7 @@ std::optional 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 {}; } } @@ -180,3 +180,26 @@ LSTATUS CRegistrySettings::setDefaultRefresh(DWORD refresh) { return RegSetValueEx(hKey, L"DefaultRefresh", 0, REG_DWORD, (LPBYTE) &refresh, sizeof(DWORD)); } + +std::optional 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)); +} diff --git a/idd/LGIddHelper/CRegistrySettings.h b/idd/LGIddHelper/CRegistrySettings.h index 83af6a50..ee3e1504 100644 --- a/idd/LGIddHelper/CRegistrySettings.h +++ b/idd/LGIddHelper/CRegistrySettings.h @@ -49,4 +49,7 @@ public: std::optional getDefaultRefresh(); LSTATUS setDefaultRefresh(DWORD refresh); + + std::optional getNoGPU(); + LSTATUS setNoGPU(bool noGPU); }; From fec805fbe2f0ed7b65d93d472e0fa4a659bfef7a Mon Sep 17 00:00:00 2001 From: Quantum Date: Wed, 3 Jun 2026 00:47:56 -0400 Subject: [PATCH 3/5] [idd] helper: show notification when no GPU is detected --- idd/LGIddHelper/CNotifyWindow.cpp | 28 ++++++++++++++++++++++++++++ idd/LGIddHelper/CNotifyWindow.h | 1 + 2 files changed, 29 insertions(+) diff --git a/idd/LGIddHelper/CNotifyWindow.cpp b/idd/LGIddHelper/CNotifyWindow.cpp index 34b90526..50c3490a 100644 --- a/idd/LGIddHelper/CNotifyWindow.cpp +++ b/idd/LGIddHelper/CNotifyWindow.cpp @@ -169,7 +169,35 @@ void CNotifyWindow::registerIcon() 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() diff --git a/idd/LGIddHelper/CNotifyWindow.h b/idd/LGIddHelper/CNotifyWindow.h index 3f8c3ad1..3f30c1fd 100644 --- a/idd/LGIddHelper/CNotifyWindow.h +++ b/idd/LGIddHelper/CNotifyWindow.h @@ -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; From 29d02ddbb90deb949ddda5adf50087626cc7e14c Mon Sep 17 00:00:00 2001 From: Quantum Date: Wed, 3 Jun 2026 01:19:34 -0400 Subject: [PATCH 4/5] [idd] helper: add checkbox widget --- idd/LGIddHelper/CCheckbox.cpp | 38 +++++++++++++++++++++++++++++ idd/LGIddHelper/CCheckbox.h | 31 +++++++++++++++++++++++ idd/LGIddHelper/LGIddHelper.vcxproj | 2 ++ 3 files changed, 71 insertions(+) create mode 100644 idd/LGIddHelper/CCheckbox.cpp create mode 100644 idd/LGIddHelper/CCheckbox.h diff --git a/idd/LGIddHelper/CCheckbox.cpp b/idd/LGIddHelper/CCheckbox.cpp new file mode 100644 index 00000000..321c777e --- /dev/null +++ b/idd/LGIddHelper/CCheckbox.cpp @@ -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 +#include + +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); +} diff --git a/idd/LGIddHelper/CCheckbox.h b/idd/LGIddHelper/CCheckbox.h new file mode 100644 index 00000000..8f6abde9 --- /dev/null +++ b/idd/LGIddHelper/CCheckbox.h @@ -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); +}; diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj b/idd/LGIddHelper/LGIddHelper.vcxproj index 5bdb657b..3acf84dc 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj +++ b/idd/LGIddHelper/LGIddHelper.vcxproj @@ -180,6 +180,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + @@ -197,6 +198,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + From e74f58afb4c0d8bd6b8759ae9879ea08705254f1 Mon Sep 17 00:00:00 2001 From: Quantum Date: Wed, 3 Jun 2026 01:20:15 -0400 Subject: [PATCH 5/5] [idd] helper: allow no GPU warning to be configured --- idd/LGIddHelper/CConfigWindow.cpp | 27 +++++++++++++++++++++++---- idd/LGIddHelper/CConfigWindow.h | 5 +++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/idd/LGIddHelper/CConfigWindow.cpp b/idd/LGIddHelper/CConfigWindow.cpp index 398f7e8c..378c6462 100644 --- a/idd/LGIddHelper/CConfigWindow.cpp +++ b/idd/LGIddHelper/CConfigWindow.cpp @@ -23,6 +23,7 @@ #include "CGroupBox.h" #include "CEditWidget.h" #include "CButton.h" +#include "CCheckbox.h" #include #include #include @@ -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_autosizeGroup, *m_defRefreshLabel, *m_defRefresh, *m_defRefreshHz, + *m_prefGroup, *m_prefNoGPU, })) SendMessage(child, WM_SETFONT, (WPARAM)m_font.Get(), 1); } @@ -167,6 +170,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); @@ -203,6 +212,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; } @@ -231,11 +243,11 @@ void CConfigWindow::onModeListSelectChange() LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) { - if (hwnd == *m_modeBox && code == LBN_SELCHANGE && m_modes) + if (m_modeBox && hwnd == *m_modeBox && code == LBN_SELCHANGE && m_modes) { onModeListSelectChange(); } - else if (hwnd == *m_modeUpdate && code == BN_CLICKED && m_modes) + else if (m_modeUpdate && hwnd == *m_modeUpdate && code == BN_CLICKED && m_modes) { int sel = m_modeBox->getSel(); if (sel == LB_ERR) @@ -264,7 +276,7 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) if (result != ERROR_SUCCESS) DEBUG_ERROR_HR((HRESULT) result, "Failed to save modes"); } - else if (hwnd == *m_modeDelete && code == BN_CLICKED && m_modes) + else if (m_modeDelete && hwnd == *m_modeDelete && code == BN_CLICKED && m_modes) { int sel = m_modeBox->getSel(); if (sel == LB_ERR) @@ -281,7 +293,7 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) updateModeList(); onModeListSelectChange(); } - else if (hwnd == *m_defRefresh && code == EN_CHANGE && m_defaultRefresh) + else if (m_defRefresh && hwnd == *m_defRefresh && code == EN_CHANGE && m_defaultRefresh) { int value; try @@ -298,5 +310,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; } diff --git a/idd/LGIddHelper/CConfigWindow.h b/idd/LGIddHelper/CConfigWindow.h index f8fbecf7..9a68b6bb 100644 --- a/idd/LGIddHelper/CConfigWindow.h +++ b/idd/LGIddHelper/CConfigWindow.h @@ -32,6 +32,7 @@ class CListBox; class CGroupBox; class CEditWidget; class CButton; +class CCheckbox; class CConfigWindow : public CWindow { @@ -57,12 +58,16 @@ class CConfigWindow : public CWindow std::unique_ptr m_defRefresh; std::unique_ptr m_defRefreshHz; + std::unique_ptr m_prefGroup; + std::unique_ptr m_prefNoGPU; + std::function m_onDestroy; double m_scale; Microsoft::WRL::Wrappers::HandleT m_font; CRegistrySettings m_settings; std::optional> m_modes; std::optional m_defaultRefresh; + std::optional m_noGPU; void getMinimumSize(LONG &width, LONG &height); void updateFont();