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/CConfigWindow.cpp b/idd/LGIddHelper/CConfigWindow.cpp index e111f8eb..297eda30 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_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; } diff --git a/idd/LGIddHelper/CConfigWindow.h b/idd/LGIddHelper/CConfigWindow.h index 8b294e43..b8043391 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 { @@ -58,12 +59,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(); diff --git a/idd/LGIddHelper/CNotifyWindow.cpp b/idd/LGIddHelper/CNotifyWindow.cpp index 118cbf4c..50c3490a 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,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() 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; diff --git a/idd/LGIddHelper/CRegistrySettings.cpp b/idd/LGIddHelper/CRegistrySettings.cpp index 1cda0708..bab9400c 100644 --- a/idd/LGIddHelper/CRegistrySettings.cpp +++ b/idd/LGIddHelper/CRegistrySettings.cpp @@ -177,7 +177,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 {}; } } @@ -186,3 +186,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 49801985..8a330054 100644 --- a/idd/LGIddHelper/CRegistrySettings.h +++ b/idd/LGIddHelper/CRegistrySettings.h @@ -50,4 +50,7 @@ public: std::optional getDefaultRefresh(); LSTATUS setDefaultRefresh(DWORD refresh); + + std::optional getNoGPU(); + LSTATUS setNoGPU(bool noGPU); }; 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..3acf84dc 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj +++ b/idd/LGIddHelper/LGIddHelper.vcxproj @@ -180,6 +180,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + @@ -190,12 +191,14 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + + @@ -205,6 +208,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 +