mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 02:06:36 +02:00
common: refactor common library pt2 (#8588)
- remove common lib - split settings, remove common-md - move ipc interop/kb_layout to interop - rename core -> settings, settings -> old_settings - os-detect header-only; interop -> PowerToysInterop - split notifications, move single-use headers where they're used - winstore lib - rename com utils - rename Updating and Telemetry projects - rename core -> settings-ui and remove examples folder - rename settings-ui folder + consisent common/version include
This commit is contained in:
27
src/common/utils/appMutex.h
Normal file
27
src/common/utils/appMutex.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
#include "wil/resource.h"
|
||||
#include <lmcons.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr inline wchar_t POWERTOYS_MSI_MUTEX_NAME[] = L"Local\\PowerToyRunMutex";
|
||||
constexpr inline wchar_t POWERTOYS_MSIX_MUTEX_NAME[] = L"Local\\PowerToyMSIXRunMutex";
|
||||
constexpr inline wchar_t POWERTOYS_BOOTSTRAPPER_MUTEX_NAME[] = L"PowerToysBootstrapperMutex";
|
||||
}
|
||||
|
||||
inline wil::unique_mutex_nothrow createAppMutex(std::wstring mutexName)
|
||||
{
|
||||
wchar_t username[UNLEN + 1];
|
||||
DWORD username_length = UNLEN + 1;
|
||||
GetUserNameW(username, &username_length);
|
||||
mutexName += username;
|
||||
wil::unique_mutex_nothrow result{ CreateMutexW(nullptr, TRUE, mutexName.c_str()) };
|
||||
|
||||
return GetLastError() == ERROR_ALREADY_EXISTS ? wil::unique_mutex_nothrow{} : std::move(result);
|
||||
}
|
||||
61
src/common/utils/com_object_factory.h
Normal file
61
src/common/utils/com_object_factory.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <Unknwn.h>
|
||||
#include <winrt/base.h>
|
||||
#include <atomic>
|
||||
#include <shlwapi.h>
|
||||
|
||||
template<typename T>
|
||||
class com_object_factory : public IClassFactory
|
||||
{
|
||||
public:
|
||||
HRESULT __stdcall QueryInterface(const IID& riid, void** ppv) override
|
||||
{
|
||||
static const QITAB qit[] = {
|
||||
QITABENT(com_object_factory, IClassFactory),
|
||||
{ 0 }
|
||||
};
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
|
||||
ULONG __stdcall AddRef() override
|
||||
{
|
||||
return ++_refCount;
|
||||
}
|
||||
|
||||
ULONG __stdcall Release() override
|
||||
{
|
||||
LONG refCount = --_refCount;
|
||||
return refCount;
|
||||
}
|
||||
|
||||
HRESULT __stdcall CreateInstance(IUnknown* punkOuter, const IID& riid, void** ppv)
|
||||
{
|
||||
*ppv = nullptr;
|
||||
HRESULT hr;
|
||||
if (punkOuter)
|
||||
{
|
||||
hr = CLASS_E_NOAGGREGATION;
|
||||
}
|
||||
else
|
||||
{
|
||||
T* psrm = new (std::nothrow) T();
|
||||
HRESULT hr = psrm ? S_OK : E_OUTOFMEMORY;
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = psrm->QueryInterface(riid, ppv);
|
||||
psrm->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT __stdcall LockServer(BOOL)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<long> _refCount;
|
||||
};
|
||||
271
src/common/utils/elevation.h
Normal file
271
src/common/utils/elevation.h
Normal file
@@ -0,0 +1,271 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <sddl.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// Returns true if the current process is running with elevated privileges
|
||||
inline bool is_process_elevated(const bool use_cached_value = true)
|
||||
{
|
||||
auto detection_func = []() {
|
||||
HANDLE token = nullptr;
|
||||
bool elevated = false;
|
||||
|
||||
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
|
||||
{
|
||||
TOKEN_ELEVATION elevation;
|
||||
DWORD size;
|
||||
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size))
|
||||
{
|
||||
elevated = (elevation.TokenIsElevated != 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (token)
|
||||
{
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
return elevated;
|
||||
};
|
||||
static const bool cached_value = detection_func();
|
||||
return use_cached_value ? cached_value : detection_func();
|
||||
}
|
||||
|
||||
// Drops the elevated privileges if present
|
||||
inline bool drop_elevated_privileges()
|
||||
{
|
||||
HANDLE token = nullptr;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT | WRITE_OWNER, &token))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PSID medium_sid = NULL;
|
||||
if (!::ConvertStringSidToSid(SDDL_ML_MEDIUM, &medium_sid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TOKEN_MANDATORY_LABEL label = { 0 };
|
||||
label.Label.Attributes = SE_GROUP_INTEGRITY;
|
||||
label.Label.Sid = medium_sid;
|
||||
DWORD size = (DWORD)sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(medium_sid);
|
||||
|
||||
BOOL result = SetTokenInformation(token, TokenIntegrityLevel, &label, size);
|
||||
LocalFree(medium_sid);
|
||||
CloseHandle(token);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Run command as elevated user, returns true if succeeded
|
||||
inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params)
|
||||
{
|
||||
SHELLEXECUTEINFOW exec_info = { 0 };
|
||||
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
exec_info.lpVerb = L"runas";
|
||||
exec_info.lpFile = file.c_str();
|
||||
exec_info.lpParameters = params.c_str();
|
||||
exec_info.hwnd = 0;
|
||||
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
exec_info.lpDirectory = 0;
|
||||
exec_info.hInstApp = 0;
|
||||
exec_info.nShow = SW_SHOWDEFAULT;
|
||||
|
||||
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
|
||||
}
|
||||
|
||||
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
|
||||
inline bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid)
|
||||
{
|
||||
auto executable_args = L"\"" + file + L"\"";
|
||||
if (!params.empty())
|
||||
{
|
||||
executable_args += L" " + params;
|
||||
}
|
||||
|
||||
HWND hwnd = GetShellWindow();
|
||||
if (!hwnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
|
||||
winrt::handle process{ OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid) };
|
||||
if (!process)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T size = 0;
|
||||
|
||||
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
|
||||
auto pproc_buffer = std::make_unique<char[]>(size);
|
||||
auto pptal = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(pproc_buffer.get());
|
||||
|
||||
if (!InitializeProcThreadAttributeList(pptal, 1, 0, &size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE process_handle = process.get();
|
||||
if (!pptal || !UpdateProcThreadAttribute(pptal,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
|
||||
&process_handle,
|
||||
sizeof(process_handle),
|
||||
nullptr,
|
||||
nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
STARTUPINFOEX siex = { 0 };
|
||||
siex.lpAttributeList = pptal;
|
||||
siex.StartupInfo.cb = sizeof(siex);
|
||||
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
auto succeeded = CreateProcessW(file.c_str(),
|
||||
const_cast<LPWSTR>(executable_args.c_str()),
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
EXTENDED_STARTUPINFO_PRESENT,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&siex.StartupInfo,
|
||||
&pi);
|
||||
if (succeeded)
|
||||
{
|
||||
if (pi.hProcess)
|
||||
{
|
||||
if (returnPid)
|
||||
{
|
||||
*returnPid = GetProcessId(pi.hProcess);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
if (pi.hThread)
|
||||
{
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
}
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
// Run command with the same elevation, returns true if succeeded
|
||||
inline bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid)
|
||||
{
|
||||
auto executable_args = L"\"" + file + L"\"";
|
||||
if (!params.empty())
|
||||
{
|
||||
executable_args += L" " + params;
|
||||
}
|
||||
|
||||
STARTUPINFO si = { 0 };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
auto succeeded = CreateProcessW(file.c_str(),
|
||||
const_cast<LPWSTR>(executable_args.c_str()),
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi);
|
||||
|
||||
if (succeeded)
|
||||
{
|
||||
if (pi.hProcess)
|
||||
{
|
||||
if (returnPid)
|
||||
{
|
||||
*returnPid = GetProcessId(pi.hProcess);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
|
||||
if (pi.hThread)
|
||||
{
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
// Returns true if the current process is running from administrator account
|
||||
// The function returns true in case of error since we want to return false
|
||||
// only in case of a positive verification that the user is not an admin.
|
||||
inline bool check_user_is_admin()
|
||||
{
|
||||
auto freeMemory = [](PSID pSID, PTOKEN_GROUPS pGroupInfo) {
|
||||
if (pSID)
|
||||
{
|
||||
FreeSid(pSID);
|
||||
}
|
||||
if (pGroupInfo)
|
||||
{
|
||||
GlobalFree(pGroupInfo);
|
||||
}
|
||||
};
|
||||
|
||||
HANDLE hToken;
|
||||
DWORD dwSize = 0;
|
||||
PTOKEN_GROUPS pGroupInfo;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
|
||||
PSID pSID = NULL;
|
||||
|
||||
// Open a handle to the access token for the calling process.
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call GetTokenInformation to get the buffer size.
|
||||
if (!GetTokenInformation(hToken, TokenGroups, NULL, dwSize, &dwSize))
|
||||
{
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the buffer.
|
||||
pGroupInfo = (PTOKEN_GROUPS)GlobalAlloc(GPTR, dwSize);
|
||||
|
||||
// Call GetTokenInformation again to get the group information.
|
||||
if (!GetTokenInformation(hToken, TokenGroups, pGroupInfo, dwSize, &dwSize))
|
||||
{
|
||||
freeMemory(pSID, pGroupInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
if (!AllocateAndInitializeSid(&SIDAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSID))
|
||||
{
|
||||
freeMemory(pSID, pGroupInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop through the group SIDs looking for the administrator SID.
|
||||
for (DWORD i = 0; i < pGroupInfo->GroupCount; ++i)
|
||||
{
|
||||
if (EqualSid(pSID, pGroupInfo->Groups[i].Sid))
|
||||
{
|
||||
freeMemory(pSID, pGroupInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
freeMemory(pSID, pGroupInfo);
|
||||
return false;
|
||||
}
|
||||
100
src/common/utils/exec.h
Normal file
100
src/common/utils/exec.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <wil/resource.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
inline std::optional<std::string> exec_and_read_output(const std::wstring_view command, DWORD timeout_ms = 30000)
|
||||
{
|
||||
SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) };
|
||||
saAttr.bInheritHandle = false;
|
||||
|
||||
constexpr size_t bufferSize = 4096;
|
||||
// We must use a named pipe for async I/O
|
||||
char pipename[MAX_PATH + 1];
|
||||
if (!GetTempFileNameA(R"(\\.\pipe\)", "tmp", 1, pipename))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wil::unique_handle readPipe{ CreateNamedPipeA(pipename, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &saAttr) };
|
||||
|
||||
saAttr.bInheritHandle = true;
|
||||
wil::unique_handle writePipe{ CreateFileA(pipename, GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
|
||||
if (!readPipe || !writePipe)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION piProcInfo{};
|
||||
STARTUPINFOW siStartInfo{ sizeof(siStartInfo) };
|
||||
|
||||
siStartInfo.hStdError = writePipe.get();
|
||||
siStartInfo.hStdOutput = writePipe.get();
|
||||
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
siStartInfo.wShowWindow = SW_HIDE;
|
||||
|
||||
std::wstring cmdLine{ command };
|
||||
if (!CreateProcessW(nullptr,
|
||||
cmdLine.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
true,
|
||||
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&siStartInfo,
|
||||
&piProcInfo))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
// Child process inherited the write end of the pipe, we can close it now
|
||||
writePipe.reset();
|
||||
|
||||
auto closeProcessHandles = wil::scope_exit([&] {
|
||||
CloseHandle(piProcInfo.hThread);
|
||||
CloseHandle(piProcInfo.hProcess);
|
||||
});
|
||||
|
||||
std::string childOutput;
|
||||
bool processExited = false;
|
||||
for (;;)
|
||||
{
|
||||
char buffer[bufferSize];
|
||||
DWORD gotBytes = 0;
|
||||
wil::unique_handle IOEvent{ CreateEventW(nullptr, true, false, nullptr) };
|
||||
OVERLAPPED overlapped{ .hEvent = IOEvent.get() };
|
||||
ReadFile(readPipe.get(), buffer, sizeof(buffer), nullptr, &overlapped);
|
||||
|
||||
const std::array<HANDLE, 2> handlesToWait = { overlapped.hEvent, piProcInfo.hProcess };
|
||||
switch (WaitForMultipleObjects(1 + !processExited, handlesToWait.data(), false, timeout_ms))
|
||||
{
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
if (!processExited)
|
||||
{
|
||||
// When the process exits, we can reduce timeout and read the rest of the output w/o possibly big timeout
|
||||
timeout_ms = 1000;
|
||||
processExited = true;
|
||||
closeProcessHandles.reset();
|
||||
}
|
||||
[[fallthrough]];
|
||||
case WAIT_OBJECT_0:
|
||||
if (GetOverlappedResultEx(readPipe.get(), &overlapped, &gotBytes, timeout_ms, true))
|
||||
{
|
||||
childOutput += std::string_view{ buffer, gotBytes };
|
||||
break;
|
||||
}
|
||||
// Timeout
|
||||
[[fallthrough]];
|
||||
default:
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
CancelIo(readPipe.get());
|
||||
return childOutput;
|
||||
}
|
||||
73
src/common/utils/json.h
Normal file
73
src/common/utils/json.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Data.Json.h>
|
||||
|
||||
#include <optional>
|
||||
#include <fstream>
|
||||
|
||||
namespace json
|
||||
{
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
|
||||
inline std::optional<JsonObject> from_file(std::wstring_view file_name)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(file_name.data(), std::ios::binary);
|
||||
if (file.is_open())
|
||||
{
|
||||
using isbi = std::istreambuf_iterator<char>;
|
||||
std::string obj_str{ isbi{ file }, isbi{} };
|
||||
return JsonValue::Parse(winrt::to_hstring(obj_str)).GetObjectW();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
inline void to_file(std::wstring_view file_name, const JsonObject& obj)
|
||||
{
|
||||
std::wstring obj_str{ obj.Stringify().c_str() };
|
||||
std::ofstream{ file_name.data(), std::ios::binary } << winrt::to_string(obj_str);
|
||||
}
|
||||
|
||||
inline bool has(
|
||||
const json::JsonObject& o,
|
||||
std::wstring_view name,
|
||||
const json::JsonValueType type = JsonValueType::Object)
|
||||
{
|
||||
return o.HasKey(name) && o.GetNamedValue(name).ValueType() == type;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::enable_if_t<std::is_arithmetic_v<T>, JsonValue> value(const T arithmetic)
|
||||
{
|
||||
return json::JsonValue::CreateNumberValue(arithmetic);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::enable_if_t<!std::is_arithmetic_v<T>, JsonValue> value(T s)
|
||||
{
|
||||
return json::JsonValue::CreateStringValue(s);
|
||||
}
|
||||
|
||||
inline JsonValue value(const bool boolean)
|
||||
{
|
||||
return json::JsonValue::CreateBooleanValue(boolean);
|
||||
}
|
||||
|
||||
inline JsonValue value(JsonObject value)
|
||||
{
|
||||
return value.as<JsonValue>();
|
||||
}
|
||||
|
||||
inline JsonValue value(JsonValue value)
|
||||
{
|
||||
return value; // identity function overload for convenience
|
||||
}
|
||||
}
|
||||
31
src/common/utils/os-detect.h
Normal file
31
src/common/utils/os-detect.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
|
||||
// The following three helper functions determine if the user has a build version higher than or equal to 19h1, as that is a requirement for xaml islands
|
||||
// Source : Microsoft-ui-xaml github
|
||||
// Link: https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp
|
||||
template<uint16_t APIVersion>
|
||||
inline bool IsAPIContractVxAvailable()
|
||||
{
|
||||
static bool isAPIContractVxAvailable = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", APIVersion);
|
||||
|
||||
return isAPIContractVxAvailable;
|
||||
}
|
||||
|
||||
inline bool IsAPIContractV8Available()
|
||||
{
|
||||
return IsAPIContractVxAvailable<8>();
|
||||
}
|
||||
|
||||
inline bool Is19H1OrHigher()
|
||||
{
|
||||
return IsAPIContractV8Available();
|
||||
}
|
||||
|
||||
// This function returns true if the build is 19h1 or higher, so that we deploy the new settings.
|
||||
// It returns false otherwise.
|
||||
inline bool UseNewSettings()
|
||||
{
|
||||
return Is19H1OrHigher();
|
||||
}
|
||||
48
src/common/utils/processApi.h
Normal file
48
src/common/utils/processApi.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <wil/resource.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <Psapi.h>
|
||||
#include <string_view>
|
||||
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
inline std::vector<wil::unique_process_handle> getProcessHandlesByName(const std::wstring_view processName, DWORD handleAccess)
|
||||
{
|
||||
std::vector<wil::unique_process_handle> result;
|
||||
DWORD bytesRequired;
|
||||
std::vector<DWORD> processIds;
|
||||
processIds.resize(4096 / sizeof(processIds[0]));
|
||||
auto processIdSize = static_cast<DWORD>(size(processIds) * sizeof(processIds[0]));
|
||||
EnumProcesses(processIds.data(), processIdSize, &bytesRequired);
|
||||
while (bytesRequired == processIdSize)
|
||||
{
|
||||
processIdSize *= 2;
|
||||
processIds.resize(processIdSize / sizeof(processIds[0]));
|
||||
EnumProcesses(processIds.data(), processIdSize, &bytesRequired);
|
||||
}
|
||||
processIds.resize(bytesRequired / sizeof(processIds[0]));
|
||||
|
||||
handleAccess |= PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ;
|
||||
for (const DWORD processId : processIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
wil::unique_process_handle hProcess{ OpenProcess(handleAccess, FALSE, processId) };
|
||||
wchar_t name[MAX_PATH + 1];
|
||||
if (!hProcess || !GetProcessImageFileNameW(hProcess.get(), name, MAX_PATH))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (processName == PathFindFileNameW(name))
|
||||
{
|
||||
result.push_back(std::move(hProcess));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
101
src/common/utils/process_path.h
Normal file
101
src/common/utils/process_path.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
|
||||
// Get the executable path or module name for modern apps
|
||||
inline std::wstring get_process_path(DWORD pid) noexcept
|
||||
{
|
||||
auto process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, TRUE, pid);
|
||||
std::wstring name;
|
||||
if (process != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
name.resize(MAX_PATH);
|
||||
DWORD name_length = static_cast<DWORD>(name.length());
|
||||
if (QueryFullProcessImageNameW(process, 0, (LPWSTR)name.data(), &name_length) == 0)
|
||||
{
|
||||
name_length = 0;
|
||||
}
|
||||
name.resize(name_length);
|
||||
CloseHandle(process);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// Get the executable path or module name for modern apps
|
||||
inline std::wstring get_process_path(HWND window) noexcept
|
||||
{
|
||||
const static std::wstring app_frame_host = L"ApplicationFrameHost.exe";
|
||||
DWORD pid{};
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
auto name = get_process_path(pid);
|
||||
if (name.length() >= app_frame_host.length() &&
|
||||
name.compare(name.length() - app_frame_host.length(), app_frame_host.length(), app_frame_host) == 0)
|
||||
{
|
||||
// It is a UWP app. We will enumerate the windows and look for one created
|
||||
// by something with a different PID
|
||||
DWORD new_pid = pid;
|
||||
EnumChildWindows(
|
||||
window, [](HWND hwnd, LPARAM param) -> BOOL {
|
||||
auto new_pid_ptr = reinterpret_cast<DWORD*>(param);
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid != *new_pid_ptr)
|
||||
{
|
||||
*new_pid_ptr = pid;
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
},
|
||||
reinterpret_cast<LPARAM>(&new_pid));
|
||||
// If we have a new pid, get the new name.
|
||||
if (new_pid != pid)
|
||||
{
|
||||
return get_process_path(new_pid);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
inline std::wstring get_module_filename(HMODULE mod = nullptr)
|
||||
{
|
||||
wchar_t buffer[MAX_PATH + 1];
|
||||
DWORD actual_length = GetModuleFileNameW(mod, buffer, MAX_PATH);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
const DWORD long_path_length = 0xFFFF; // should be always enough
|
||||
std::wstring long_filename(long_path_length, L'\0');
|
||||
actual_length = GetModuleFileNameW(mod, long_filename.data(), long_path_length);
|
||||
return long_filename.substr(0, actual_length);
|
||||
}
|
||||
return { buffer, actual_length };
|
||||
}
|
||||
|
||||
inline std::wstring get_module_folderpath(HMODULE mod = nullptr, const bool removeFilename = true)
|
||||
{
|
||||
wchar_t buffer[MAX_PATH + 1];
|
||||
DWORD actual_length = GetModuleFileNameW(mod, buffer, MAX_PATH);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
const DWORD long_path_length = 0xFFFF; // should be always enough
|
||||
std::wstring long_filename(long_path_length, L'\0');
|
||||
actual_length = GetModuleFileNameW(mod, long_filename.data(), long_path_length);
|
||||
PathRemoveFileSpecW(long_filename.data());
|
||||
long_filename.resize(std::wcslen(long_filename.data()));
|
||||
long_filename.shrink_to_fit();
|
||||
return long_filename;
|
||||
}
|
||||
|
||||
if (removeFilename)
|
||||
{
|
||||
PathRemoveFileSpecW(buffer);
|
||||
}
|
||||
return { buffer, (UINT)lstrlenW(buffer) };
|
||||
}
|
||||
23
src/common/utils/resources.h
Normal file
23
src/common/utils/resources.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
// Get a string from the resource file
|
||||
inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback)
|
||||
{
|
||||
wchar_t* text_ptr;
|
||||
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
|
||||
if (length == 0)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
else
|
||||
{
|
||||
return { text_ptr, static_cast<std::size_t>(length) };
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
// Wrapper for getting a string from the resource file. Returns the resource id text when fails.
|
||||
#define GET_RESOURCE_STRING(resource_id) get_resource_string(resource_id, reinterpret_cast<HINSTANCE>(&__ImageBase), L#resource_id)
|
||||
50
src/common/utils/string_utils.h
Normal file
50
src/common/utils/string_utils.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
template<typename CharT>
|
||||
struct default_trim_arg
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct default_trim_arg<char>
|
||||
{
|
||||
static inline constexpr std::string_view value = " \t\r\n";
|
||||
};
|
||||
|
||||
template<>
|
||||
struct default_trim_arg<wchar_t>
|
||||
{
|
||||
static inline constexpr std::wstring_view value = L" \t\r\n";
|
||||
};
|
||||
|
||||
template<typename CharT>
|
||||
inline std::basic_string_view<CharT> left_trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>::value)
|
||||
{
|
||||
s.remove_prefix(std::min<size_t>(s.find_first_not_of(chars_to_trim), size(s)));
|
||||
return s;
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
inline std::basic_string_view<CharT> right_trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>::value)
|
||||
{
|
||||
s.remove_suffix(std::min<size_t>(size(s) - s.find_last_not_of(chars_to_trim) - 1, size(s)));
|
||||
return s;
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
inline std::basic_string_view<CharT> trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>::value)
|
||||
{
|
||||
return left_trim(right_trim(s, chars_to_trim), chars_to_trim);
|
||||
}
|
||||
|
||||
inline void replace_chars(std::string& s, const std::string_view chars_to_replace, const char replacement_char)
|
||||
{
|
||||
for (const char c : chars_to_replace)
|
||||
{
|
||||
std::replace(begin(s), end(s), c, replacement_char);
|
||||
}
|
||||
}
|
||||
57
src/common/utils/timeutil.h
Normal file
57
src/common/utils/timeutil.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
namespace timeutil
|
||||
{
|
||||
inline std::wstring to_string(const time_t time)
|
||||
{
|
||||
return std::to_wstring(static_cast<uint64_t>(time));
|
||||
}
|
||||
|
||||
inline std::optional<std::time_t> from_string(const std::wstring& s)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint64_t i = std::stoull(s);
|
||||
return static_cast<std::time_t>(i);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::time_t now()
|
||||
{
|
||||
return winrt::clock::to_time_t(winrt::clock::now());
|
||||
}
|
||||
|
||||
namespace diff
|
||||
{
|
||||
inline int64_t in_seconds(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from));
|
||||
}
|
||||
|
||||
inline int64_t in_minutes(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / 60);
|
||||
}
|
||||
|
||||
inline int64_t in_hours(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / 3600);
|
||||
}
|
||||
|
||||
inline int64_t in_days(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / (3600 * (int64_t)24));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/common/utils/winapi_error.h
Normal file
44
src/common/utils/winapi_error.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <strsafe.h>
|
||||
|
||||
inline std::optional<std::wstring> get_last_error_message(const DWORD dw)
|
||||
{
|
||||
std::optional<std::wstring> message;
|
||||
try
|
||||
{
|
||||
const auto msg = std::system_category().message(dw);
|
||||
message.emplace(begin(msg), end(msg));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
inline void show_last_error_message(const wchar_t* functionName, DWORD dw, const wchar_t* errorTitle)
|
||||
{
|
||||
const auto system_message = get_last_error_message(dw);
|
||||
if (!system_message.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
LPWSTR lpDisplayBuf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, (system_message->size() + lstrlenW(functionName) + 40) * sizeof(WCHAR));
|
||||
if (lpDisplayBuf != NULL)
|
||||
{
|
||||
StringCchPrintfW(lpDisplayBuf,
|
||||
LocalSize(lpDisplayBuf) / sizeof(WCHAR),
|
||||
L"%s: %s (%d)",
|
||||
functionName,
|
||||
system_message->c_str(),
|
||||
dw);
|
||||
MessageBoxW(NULL, (LPCTSTR)lpDisplayBuf, errorTitle, MB_OK | MB_ICONERROR);
|
||||
LocalFree(lpDisplayBuf);
|
||||
}
|
||||
}
|
||||
59
src/common/utils/window.h
Normal file
59
src/common/utils/window.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#pragma comment(lib, "dwmapi.lib")
|
||||
|
||||
// Initializes and runs windows message loop
|
||||
inline int run_message_loop(const bool until_idle = false, const std::optional<uint32_t> timeout_seconds = {})
|
||||
{
|
||||
MSG msg{};
|
||||
bool stop = false;
|
||||
UINT_PTR timerId = 0;
|
||||
if (timeout_seconds.has_value())
|
||||
{
|
||||
timerId = SetTimer(nullptr, 0, *timeout_seconds * 1000, nullptr);
|
||||
}
|
||||
|
||||
while (!stop && GetMessageW(&msg, nullptr, 0, 0))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
stop = until_idle && !PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
|
||||
stop = stop || (msg.message == WM_TIMER && msg.wParam == timerId);
|
||||
}
|
||||
if (timeout_seconds.has_value())
|
||||
{
|
||||
KillTimer(nullptr, timerId);
|
||||
}
|
||||
return static_cast<int>(msg.wParam);
|
||||
}
|
||||
|
||||
// Check if window is part of the shell or the taskbar.
|
||||
inline bool is_system_window(HWND hwnd, const char* class_name)
|
||||
{
|
||||
// We compare the HWND against HWND of the desktop and shell windows,
|
||||
// we also filter out some window class names know to belong to the taskbar.
|
||||
constexpr std::array system_classes = { "SysListView32", "WorkerW", "Shell_TrayWnd", "Shell_SecondaryTrayWnd", "Progman" };
|
||||
const std::array system_hwnds = { GetDesktopWindow(), GetShellWindow() };
|
||||
for (auto system_hwnd : system_hwnds)
|
||||
{
|
||||
if (hwnd == system_hwnd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto system_class : system_classes)
|
||||
{
|
||||
if (!strcmp(system_class, class_name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user