1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "nel/misc/system_utils.h"
22 #include "nel/misc/utf_string_view.h"
30 #ifdef DXGI_STATUS_OCCLUDED
31 #undef DXGI_STATUS_OCCLUDED
32 #undef DXGI_STATUS_CLIPPED
33 #undef DXGI_STATUS_NO_REDIRECTION
34 #undef DXGI_STATUS_NO_DESKTOP_ACCESS
35 #undef DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE
36 #undef DXGI_STATUS_MODE_CHANGED
37 #undef DXGI_STATUS_MODE_CHANGE_IN_PROGRESS
39 #ifdef DXGI_ERROR_INVALID_CALL
40 #undef DXGI_ERROR_INVALID_CALL
41 #undef DXGI_ERROR_NOT_FOUND
42 #undef DXGI_ERROR_MORE_DATA
43 #undef DXGI_ERROR_UNSUPPORTED
44 #undef DXGI_ERROR_DEVICE_REMOVED
45 #undef DXGI_ERROR_DEVICE_HUNG
46 #undef DXGI_ERROR_DEVICE_RESET
47 #undef DXGI_ERROR_WAS_STILL_DRAWING
48 #undef DXGI_ERROR_FRAME_STATISTICS_DISJOINT
49 #undef DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE
50 #undef DXGI_ERROR_DRIVER_INTERNAL_ERROR
51 #undef DXGI_ERROR_NONEXCLUSIVE
52 #undef DXGI_ERROR_NOT_CURRENTLY_AVAILABLE
53 #undef DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED
54 #undef DXGI_ERROR_REMOTE_OUTOFMEMORY
60 # ifdef _WIN32_WINNT_WIN7
61 // only supported by Windows 7 Platform SDK
62 # include <ShObjIdl.h>
63 # define TASKBAR_PROGRESS 1
65 #elif defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
66 #include "nel/misc/file.h"
76 static string RootKey
;
77 static const uint32 KeyMaxLength
= 1024;
81 nlWindow
CSystemUtils::s_window
= EmptyWindow
;
84 static bool s_mustUninit
= false;
87 bool CSystemUtils::init()
93 HRESULT hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
94 if (FAILED(hr
)) return false;
103 bool CSystemUtils::uninit()
111 s_mustUninit
= false;
118 void CSystemUtils::setWindow(nlWindow window
)
123 bool CSystemUtils::updateProgressBar(uint value
, uint total
)
125 #ifdef TASKBAR_PROGRESS
126 if (s_window
== NULL
)
128 nldebug("No window has be set with CSystemUtils::setWindow(), progress bar can't be displayed");
132 ITaskbarList3
*pTaskbarList
= NULL
;
134 // instanciate the taskbar control COM object
135 HRESULT hr
= CoCreateInstance(CLSID_TaskbarList
, NULL
, CLSCTX_INPROC_SERVER
, IID_PPV_ARGS(&pTaskbarList
));
136 // error can be ignored because Windows versions before Windows 7 doesn't support it
137 if (FAILED(hr
) || !pTaskbarList
) return false;
141 // update the taskbar progress
142 hr
= pTaskbarList
->SetProgressValue(s_window
, (ULONGLONG
)value
, (ULONGLONG
)total
);
146 // don't update anymore the progress
147 hr
= pTaskbarList
->SetProgressState(s_window
, value
== 0 ? TBPF_INDETERMINATE
:TBPF_NOPROGRESS
);
150 // release the interface
151 pTaskbarList
->Release();
153 #endif // TASKBAR_PROGRESS
158 bool CSystemUtils::copyTextToClipboard(const std::string
&text
)
160 if (text
.empty()) return false;
165 if (OpenClipboard(NULL
))
167 // check if unicode format is supported by clipboard
168 bool isUnicode
= (IsClipboardFormatAvailable(CF_UNICODETEXT
) == TRUE
);
170 // allocates a buffer to copy text in global memory
171 std::string textMbcs
;
172 std::wstring textWide
;
175 textMbcs
= NLMISC::utf8ToMbcs(text
); // Prefer system for API
176 if (text
.size() && !textMbcs
.size())
177 textMbcs
= CUtfStringView(text
).toAscii(); // Fallback to 7-bit ASCII
181 textWide
= NLMISC::utf8ToWide(text
); // Prefer system for API
182 if (text
.size() && !textWide
.size())
183 textWide
= CUtfStringView(text
).toWide();
185 HGLOBAL mem
= GlobalAlloc(GHND
| GMEM_DDESHARE
, isUnicode
186 ? ((textWide
.size() + 1) * sizeof(wchar_t))
187 : (textMbcs
.size() + 1));
191 // create a lock on this buffer
192 void *hLock
= GlobalLock(mem
);
195 // copy text to this buffer
197 wcscpy((wchar_t *)hLock
, textWide
.c_str());
199 strcpy((char *)hLock
, textMbcs
.c_str());
207 // set new data to clipboard in the right format
208 SetClipboardData(isUnicode
? CF_UNICODETEXT
: CF_TEXT
, mem
);
221 bool CSystemUtils::pasteTextFromClipboard(std::string
&text
)
226 if (OpenClipboard(NULL
))
228 // check if unicode format is supported by clipboard
229 bool isUnicode
= (IsClipboardFormatAvailable(CF_UNICODETEXT
) == TRUE
);
231 // get data from clipboard (if not of this type, they are converted)
232 // warning, this code can't be debuggued in VC++ IDE, hObj will be always NULL
233 HANDLE hObj
= GetClipboardData(isUnicode
? CF_UNICODETEXT
: CF_TEXT
);
237 // create a lock on clipboard data
238 void *hLock
= GlobalLock(hObj
);
242 // retrieve clipboard data
245 const wchar_t *str
= (const wchar_t *)hLock
;
246 text
= NLMISC::wideToUtf8(str
); // Prefer system for API
247 if (!text
.size() && str
[0])
248 text
= CUtfStringView(str
).toUtf8();
250 text
= CUtfStringView(text
).toUtf8(true); // Sanitize UTF-8 user input
254 const char *str
= (const char *)hLock
;
255 text
= NLMISC::mbcsToUtf8(str
); // Prefer system for API
256 if (!text
.size() && str
[0])
257 text
= CUtfStringView(str
).toAscii(); // Fallback to 7-bit ASCII
259 text
= CUtfStringView(text
).toUtf8(true); // Sanitize UTF-8 user input
276 bool CSystemUtils::supportUnicode()
278 static bool init
= false;
279 static bool unicodeSupported
= false;
285 osvi
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
287 // get Windows version
288 if (GetVersionExA(&osvi
))
290 if (osvi
.dwPlatformId
== VER_PLATFORM_WIN32_NT
)
292 // unicode is supported since Windows NT 4.0
293 if (osvi
.dwMajorVersion
>= 4)
295 unicodeSupported
= true;
300 unicodeSupported
= true;
303 return unicodeSupported
;
306 bool CSystemUtils::isAzertyKeyboard()
309 uint16 klId
= uint16((uintptr_t)GetKeyboardLayout(0) & 0xFFFF);
310 // 0x040c is French, 0x080c is Belgian
311 if (klId
== 0x040c || klId
== 0x080c)
317 bool CSystemUtils::isScreensaverEnabled()
321 // old code, is not working anymore
323 // SystemParametersInfoA(SPI_GETSCREENSAVEACTIVE, 0, &bRetValue, 0);
324 // res = (bRetValue == TRUE);
325 HKEY hKeyScreenSaver
= NULL
;
326 LSTATUS lReturn
= RegOpenKeyExA(HKEY_CURRENT_USER
, "Control Panel\\Desktop", 0, KEY_QUERY_VALUE
, &hKeyScreenSaver
);
327 if (lReturn
== ERROR_SUCCESS
)
330 DWORD dwSize
= KeyMaxLength
;
331 unsigned char Buffer
[KeyMaxLength
] = {0};
333 lReturn
= RegQueryValueExA(hKeyScreenSaver
, "SCRNSAVE.EXE", NULL
, &dwType
, NULL
, &dwSize
);
334 // if SCRNSAVE.EXE is present, check also if it's empty
335 if (lReturn
== ERROR_SUCCESS
)
336 res
= (Buffer
[0] != '\0');
338 RegCloseKey(hKeyScreenSaver
);
343 bool CSystemUtils::enableScreensaver(bool screensaver
)
347 res
= (SystemParametersInfoA(SPI_SETSCREENSAVEACTIVE
, screensaver
? TRUE
:FALSE
, NULL
, 0) == TRUE
);
352 std::string
CSystemUtils::getRootKey()
357 void CSystemUtils::setRootKey(const std::string
&root
)
362 string
CSystemUtils::getRegKey(const string
&entry
)
368 if (RegOpenKeyExW(HKEY_CURRENT_USER
, nlUtf8ToWide(RootKey
), 0, KEY_READ
, &hkey
) == ERROR_SUCCESS
)
371 DWORD dwSize
= KeyMaxLength
;
372 wchar_t buffer
[KeyMaxLength
];
374 if (RegQueryValueExW(hkey
, nlUtf8ToWide(entry
), NULL
, &dwType
, (LPBYTE
)buffer
, &dwSize
) != ERROR_SUCCESS
)
376 nlwarning("Can't get the reg key '%s'", entry
.c_str());
380 ret
= wideToUtf8(buffer
);
387 nlwarning("Can't get the reg key '%s'", entry
.c_str());
393 bool CSystemUtils::setRegKey(const string
&valueName
, const string
&value
)
400 if (RegCreateKeyExW(HKEY_CURRENT_USER
, nlUtf8ToWide(RootKey
), 0, NULL
, REG_OPTION_NON_VOLATILE
, KEY_ALL_ACCESS
, NULL
, &hkey
, &dwDisp
) == ERROR_SUCCESS
)
402 // we must use the real Unicode string size in bytes
403 std::wstring wvalue
= nlUtf8ToWide(value
);
404 if (RegSetValueExW(hkey
, nlUtf8ToWide(valueName
), 0, REG_SZ
, (const BYTE
*)wvalue
.c_str(), (wvalue
.size() + 1) * sizeof(WCHAR
)) == ERROR_SUCCESS
)
410 nlwarning("Can't set the reg key '%s' '%s'", valueName
.c_str(), value
.c_str());
417 uint
CSystemUtils::getCurrentColorDepth()
422 HWND desktopWnd
= GetDesktopWindow();
425 HDC desktopDC
= GetWindowDC(desktopWnd
);
428 depth
= (uint
) GetDeviceCaps(desktopDC
, BITSPIXEL
);
429 ReleaseDC(desktopWnd
, desktopDC
);
433 depth
= 24; // temporary fix for compilation under Linux
435 Display *display = XOpenDisplay(NULL);
438 depth = (uint) DefaultDepth(display, DefaultScreen(display));
439 XCloseDisplay(display);
446 /// Detect whether the current process is a windowed application. Return true if definitely yes, false if unknown
447 bool CSystemUtils::detectWindowedApplication()
450 if (GetConsoleWindow() == NULL
)
458 #define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p) = NULL; } }
461 typedef HRESULT (WINAPI
* LPDIRECTDRAWCREATE
)(GUID FAR
*lpGUID
, LPDIRECTDRAW FAR
*lplpDD
, IUnknown FAR
*pUnkOuter
);
462 typedef HRESULT (WINAPI
* LPCREATEDXGIFACTORY
)(REFIID
, void**);
464 static std::string
FormatError(HRESULT hr
)
466 return NLMISC::toString("%s (0x%x)", formatErrorMessage(hr
).c_str(), hr
);
488 static std::list
<SAdapter
> s_dxgiAdapters
;
490 static void EnumerateUsingDXGI(IDXGIFactory
*pDXGIFactory
)
492 nlassert(pDXGIFactory
!= NULL
);
494 for(uint index
= 0; ; ++index
)
496 IDXGIAdapter
*pAdapter
= NULL
;
497 HRESULT hr
= pDXGIFactory
->EnumAdapters(index
, &pAdapter
);
498 // DXGIERR_NOT_FOUND is expected when the end of the list is hit
499 if (FAILED(hr
)) break;
501 DXGI_ADAPTER_DESC desc
;
502 memset(&desc
, 0, sizeof(DXGI_ADAPTER_DESC
));
504 if (SUCCEEDED(pAdapter
->GetDesc(&desc
)))
508 adapter
.name
= wideToUtf8(desc
.Description
);
509 adapter
.memory
= desc
.DedicatedVideoMemory
/ 1024;
510 adapter
.found
= true;
512 nldebug("DXGI Adapter: %u - %s - DedicatedVideoMemory: %d KiB", index
, adapter
.name
.c_str(), adapter
.memory
);
514 s_dxgiAdapters
.push_back(adapter
);
517 SAFE_RELEASE(pAdapter
);
521 BOOL WINAPI
DDEnumCallbackEx(GUID FAR
* lpGUID
, LPSTR lpDriverDescription
, LPSTR lpDriverName
, LPVOID lpContext
, HMONITOR hm
)
523 SAdapter
* pAdapter
= (SAdapter
*)lpContext
;
525 if (pAdapter
->hMonitor
== hm
)
527 pAdapter
->name
= lpDriverDescription
;
528 pAdapter
->guid
= *lpGUID
;
529 pAdapter
->found
= true;
537 sint
CSystemUtils::getTotalVideoMemory()
541 #if defined(NL_OS_WINDOWS)
543 HINSTANCE hDXGI
= LoadLibraryA("dxgi.dll");
548 // We prefer the use of DXGI 1.1
549 LPCREATEDXGIFACTORY pCreateDXGIFactory
= (LPCREATEDXGIFACTORY
)GetProcAddress(hDXGI
, "CreateDXGIFactory1");
551 if (!pCreateDXGIFactory
)
553 pCreateDXGIFactory
= (LPCREATEDXGIFACTORY
)GetProcAddress(hDXGI
, "CreateDXGIFactory");
556 if (pCreateDXGIFactory
)
558 IDXGIFactory
*pDXGIFactory
= NULL
;
559 HRESULT hr
= pCreateDXGIFactory(__uuidof(IDXGIFactory
), (LPVOID
*)&pDXGIFactory
);
563 EnumerateUsingDXGI(pDXGIFactory
);
565 SAFE_RELEASE(pDXGIFactory
);
567 if (!s_dxgiAdapters
.empty())
569 // TODO: determine what adapter is used by NeL
570 res
= s_dxgiAdapters
.front().memory
;
574 nlwarning("Unable to find an DXGI adapter");
579 nlwarning("Unable to create DXGI factory");
584 nlwarning("dxgi.dll missing entry-point");
593 HMODULE hInstDDraw
= LoadLibraryA("ddraw.dll");
598 adapter
.hMonitor
= MonitorFromWindow(s_window
, MONITOR_DEFAULTTONULL
);
600 LPDIRECTDRAWENUMERATEEXA pDirectDrawEnumerateEx
= (LPDIRECTDRAWENUMERATEEXA
)GetProcAddress(hInstDDraw
, "DirectDrawEnumerateExA");
601 LPDIRECTDRAWCREATE pDDCreate
= (LPDIRECTDRAWCREATE
)GetProcAddress(hInstDDraw
, "DirectDrawCreate");
603 if (pDirectDrawEnumerateEx
&& pDDCreate
)
605 HRESULT hr
= pDirectDrawEnumerateEx(DDEnumCallbackEx
, (VOID
*)&adapter
, DDENUM_ATTACHEDSECONDARYDEVICES
);
607 if (SUCCEEDED(hr
) && adapter
.found
)
609 LPDIRECTDRAW pDDraw
= NULL
;
610 hr
= pDDCreate(&adapter
.guid
, &pDDraw
, NULL
);
614 LPDIRECTDRAW7 pDDraw7
= NULL
;
615 hr
= pDDraw
->QueryInterface(IID_IDirectDraw7
, (VOID
**)&pDDraw7
);
620 memset(&ddscaps
, 0, sizeof(DDSCAPS2
));
621 ddscaps
.dwCaps
= DDSCAPS_VIDEOMEMORY
| DDSCAPS_LOCALVIDMEM
;
623 DWORD pdwAvailableVidMem
;
624 hr
= pDDraw7
->GetAvailableVidMem(&ddscaps
, &pdwAvailableVidMem
, NULL
);
628 res
= (sint
)pdwAvailableVidMem
/ 1024;
629 nlinfo("DirectDraw Adapter: %s - DedicatedVideoMemory: %d KiB", adapter
.name
.c_str(), adapter
.memory
);
633 nlwarning("Unable to get DirectDraw available video memory: %s", FormatError(hr
).c_str());
636 SAFE_RELEASE(pDDraw7
);
640 nlwarning("Unable to query IDirectDraw7 interface: %s", FormatError(hr
).c_str());
645 nlwarning("Unable to call DirectDrawCreate: %s", FormatError(hr
).c_str());
650 nlwarning("Unable to enumerate DirectDraw adapters (%s): %s", (adapter
.found
? "found":"not found"), FormatError(hr
).c_str());
655 nlwarning("Unable to get pointer on DirectDraw functions (DirectDrawEnumerateExA %p, DirectDrawCreate %p)", pDirectDrawEnumerateEx
, pDDCreate
);
658 FreeLibrary(hInstDDraw
);
662 nlwarning("Unable to load ddraw.dll");
665 #elif defined(NL_OS_MAC)
666 // the right method is using OpenGL
671 std::string command
= "nvidia-smi -q -d MEMORY";
673 std::string out
= getCommandOutput(command
);
677 nlwarning("Unable to launch %s", command
.c_str());
681 std::vector
<std::string
> lines
;
682 explode(out
, std::string("\n"), lines
, true);
685 for(uint i
= 0; i
< lines
.size(); ++i
)
689 std::string line
= lines
[i
];
692 std::string::size_type pos
= line
.find("Total");
693 if (pos
== std::string::npos
) continue;
697 pos
= line
.find(':', pos
);
698 if (pos
== std::string::npos
) continue;
702 std::string::size_type posUnits
= line
.find(' ', pos
);
703 if (posUnits
== std::string::npos
) continue;
707 std::string memory
= line
.substr(pos
, posUnits
-pos
-1);
708 std::string units
= line
.substr(posUnits
);
710 // convert video memory to sint
711 if (NLMISC::fromString(memory
, res
))
717 else if (units
== "GB")
723 // reset to use other methods
726 nlwarning("nvidia-smi reported %d %s as wrong video memory units", res
, units
.c_str());
730 nlinfo("nvidia-smi reported %d KiB of video memory", res
);
734 // reset to use other methods
745 // under Linux, no method is really reliable...
748 std::string logFile
= "/var/log/Xorg.0.log";
750 // parse last Xorg.0.log
751 if (file
.open(logFile
, true))
757 file
.getline(buffer
, 256);
759 if (buffer
[0] == '\0') break;
761 std::string
line(buffer
);
764 std::string::size_type pos
= line
.find(") NVIDIA(");
766 if (pos
!= std::string::npos
)
768 // [ 20.883] (--) NVIDIA(0): Memory: 2097152 kBytes
769 // [ 28.515] (--) NVIDIA(0): Memory: 262144 kBytes
770 pos
= line
.find("Memory: ", pos
);
773 if (pos
== std::string::npos
) continue;
776 std::string::size_type posUnits
= line
.find(" kBytes", pos
);
778 // found units in KiB
779 if (posUnits
== std::string::npos
) continue;
781 std::string videoMemory
= line
.substr(pos
, posUnits
-pos
);
783 if (!NLMISC::fromString(videoMemory
, res
)) continue;
785 nlinfo("Xorg NVIDIA driver reported %d KiB of video memory", res
);
790 pos
= line
.find(") intel(");
792 if (pos
!= std::string::npos
)
794 // (**) intel(0): VideoRam: 131072 KB
795 pos
= line
.find("VideoRam: ", pos
);
798 if (pos
== std::string::npos
) continue;
801 std::string::size_type posUnits
= line
.find(" KB", pos
);
803 // found units in KiB
804 if (posUnits
== std::string::npos
) continue;
806 std::string videoMemory
= line
.substr(pos
, posUnits
-pos
);
808 if (!NLMISC::fromString(videoMemory
, res
)) continue;
810 nlinfo("Xorg Intel driver reported %d KiB of video memory", res
);
814 // TODO: other drivers: fglrx (ATI), radeon (ATI)
824 std::string command
= "lspci";
826 std::string out
= getCommandOutput(command
);
830 nlwarning("Unable to launch %s", command
.c_str());
834 std::vector
<std::string
> lines
;
835 std::string deviceId
;
837 explode(out
, std::string("\n"), lines
, true);
840 for(uint i
= 0; i
< lines
.size(); ++i
)
842 std::string line
= lines
[i
];
844 if (line
.find("VGA") == std::string::npos
&&
845 line
.find("3D") == std::string::npos
&&
846 line
.find("2D") == std::string::npos
)
849 std::string::size_type pos
= line
.find(' ');
851 if (pos
== std::string::npos
) continue;
854 deviceId
= line
.substr(0, pos
);
858 if (deviceId
.empty())
860 nlwarning("Unable to find a 3D device with lspci");
864 command
= "lspci -v -s " + deviceId
;
866 out
= getCommandOutput(command
);
870 nlwarning("Unable to launch %s", command
.c_str());
874 explode(out
, std::string("\n"), lines
, true);
877 for(uint i
= 0; i
< lines
.size(); ++i
)
879 std::string line
= lines
[i
];
882 std::string::size_type pos0
= line
.find("[size=");
883 if (pos0
== std::string::npos
) continue;
885 // move to first digit
889 std::string::size_type pos1
= line
.find("]", pos0
);
890 if (pos1
== std::string::npos
) continue;
894 if (line
.substr(pos1
-1, 1) == "M")
900 else if (line
.substr(pos1
-1, 1) == "K")
913 std::string sizeStr
= line
.substr(pos0
, pos1
-pos0
);
915 // convert size to integer with right units
917 if (!NLMISC::fromString(sizeStr
, tmpSize
)) continue;
921 // take the higher size (up to 256 MiB apparently)
922 if (tmpSize
> res
) res
= tmpSize
;
925 nlinfo("lspci reported %d KiB of video memory", res
);