Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/common/utils/elevation.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,23 @@ namespace
return false;
}

CComQIPtr<IShellDispatch2>(spdispShell)
->ShellExecuteW(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(workingDir),
CComVariant(L""),
CComVariant(SW_SHOWNORMAL));
CComQIPtr<IShellDispatch2> shellDispatch(spdispShell);
if (shellDispatch == nullptr)
{
Logger::warn(L"Failed to query IShellDispatch2");
return false;
}

result = shellDispatch->ShellExecuteW(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(workingDir),
CComVariant(L""),
CComVariant(SW_SHOWNORMAL));
if (FAILED(result))
{
Logger::warn(L"ShellExecuteW() failed. {}", GetErrorString(result));
return false;
}

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ namespace

namespace KeyboardEventHandlers
{
namespace ProgramLauncher
{
std::wstring GetWorkingDirectory(const std::wstring& filePath, const std::wstring& configuredDirectory)
{
if (!configuredDirectory.empty())
{
return configuredDirectory;
}

const std::filesystem::path path{ filePath };
const auto extension = path.extension().wstring();
if (_wcsicmp(extension.c_str(), L".exe") != 0 && _wcsicmp(extension.c_str(), L".com") != 0)
{
return {};
}

return path.parent_path().wstring();
}
}

// Function to handle a single key remap
intptr_t HandleSingleKeyRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept
{
Expand Down Expand Up @@ -1393,7 +1413,7 @@ namespace KeyboardEventHandlers

for (DWORD pid : processIds)
{
ShowProgram(targetPid, fileNamePart, false, false, 0);
ShowProgram(pid, fileNamePart, false, false, 0);
}

//if (!ShowProgram(targetPid, fileNamePart, false, false, 0))
Expand Down Expand Up @@ -1424,55 +1444,78 @@ namespace KeyboardEventHandlers
expandedArgs.resize(dwSize);
DWORD result = ExpandEnvironmentStrings(shortcut.runProgramArgs.c_str(), expandedArgs.data(), dwSize);

WCHAR currentDir[MAX_PATH];
WCHAR* currentDirPtr = currentDir;
result = ExpandEnvironmentStrings(shortcut.runProgramStartInDir.c_str(), currentDir, MAX_PATH);

if (shortcut.runProgramStartInDir == L"")
std::wstring currentDir;
if (shortcut.runProgramStartInDir.empty())
{
currentDirPtr = nullptr;
currentDir = ProgramLauncher::GetWorkingDirectory(fullExpandedFilePath, {});
}
else
{
DWORD dwAttrib = GetFileAttributesW(currentDir);
WCHAR expandedCurrentDir[MAX_PATH];
result = ExpandEnvironmentStrings(shortcut.runProgramStartInDir.c_str(), expandedCurrentDir, MAX_PATH);
if (result == 0 || result > ARRAYSIZE(expandedCurrentDir))
{
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
std::wstring message = L"The start in path was not valid. It could not be used.";
toast(title, message);
return;
}

currentDir = ProgramLauncher::GetWorkingDirectory(fullExpandedFilePath, expandedCurrentDir);

if (dwAttrib == INVALID_FILE_ATTRIBUTES)
DWORD dwAttrib = GetFileAttributesW(currentDir.c_str());

if (dwAttrib == INVALID_FILE_ATTRIBUTES || (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
std::wstring message = fmt::format(L"The start in path was not valid. It could not be used.", currentDir);
currentDirPtr = nullptr;
std::wstring message = L"The start in path was not valid. It could not be used.";
toast(title, message);
return;
}
}

DWORD processId = 0;
HANDLE newProcessHandle;
HANDLE newProcessHandle = nullptr;
bool processStarted = false;
const auto currentDirPtr = currentDir.empty() ? nullptr : currentDir.c_str();

if (shortcut.elevationLevel == Shortcut::ElevationLevel::Elevated)
{
newProcessHandle = run_elevated(fullExpandedFilePath, expandedArgs, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
processId = GetProcessId(newProcessHandle);
processStarted = newProcessHandle != nullptr;
}
else if (shortcut.elevationLevel == Shortcut::ElevationLevel::NonElevated)
{
run_non_elevated(fullExpandedFilePath, expandedArgs, &processId, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
if (ProgramLauncher::ShouldUseExplorerShell(shortcut.startWindowType))
{
processStarted = RunNonElevatedEx(fullExpandedFilePath, expandedArgs, currentDir);
}
else
{
processStarted = run_non_elevated(fullExpandedFilePath, expandedArgs, &processId, currentDirPtr, false);
}
}
else if (shortcut.elevationLevel == Shortcut::ElevationLevel::DifferentUser)
{
newProcessHandle = run_as_different_user(fullExpandedFilePath, expandedArgs, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
processStarted = newProcessHandle != nullptr;
}

if (newProcessHandle != nullptr)
{
processId = GetProcessId(newProcessHandle);
CloseHandle(newProcessHandle);
}

if (processId == 0)
if (!processStarted)
{
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
std::wstring message = fmt::format(L"The application might not have started.");
toast(title, message);
return;
}

if (shortcut.startWindowType == Shortcut::StartWindowType::Hidden)
if (processId != 0 && shortcut.startWindowType == Shortcut::StartWindowType::Hidden)
{
HideProgram(processId, fileNamePart, 0);
}
Expand Down Expand Up @@ -1900,7 +1943,7 @@ namespace KeyboardEventHandlers
// reports it as up, so there is no reliable way to tell whether the user is
// still physically holding the key or has released it. Re-pressing
// unconditionally would risk leaving a modifier stuck down if the user let
// go during injection the exact failure this change set prevents. Leaving
// go during injection - the exact failure this change set prevents. Leaving
// the modifier released is always safe: the user taps it again to re-engage.

return 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ namespace KeyboardManagerInput

namespace KeyboardEventHandlers
{
namespace ProgramLauncher
{
constexpr bool ShouldUseExplorerShell(Shortcut::StartWindowType startWindowType) noexcept
{
return startWindowType == Shortcut::StartWindowType::Normal;
}

std::wstring GetWorkingDirectory(const std::wstring& filePath, const std::wstring& configuredDirectory);
}

struct ResetChordsResults
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<ClCompile Include="MockedInputSanityTests.cpp" />
<ClCompile Include="SetKeyEventTests.cpp" />
<ClCompile Include="OSLevelShortcutRemappingTests.cpp" />
<ClCompile Include="ProgramLauncherTests.cpp" />
<ClCompile Include="MockedInput.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
Expand Down Expand Up @@ -81,4 +82,4 @@
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<ClCompile Include="AppSpecificShortcutRemappingTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ProgramLauncherTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
Expand All @@ -57,4 +60,4 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "pch.h"

// Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
#pragma warning(push)
#pragma warning(disable : 26466)
#include "CppUnitTest.h"
#pragma warning(pop)

#include <keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace RemappingLogicTests
{
TEST_CLASS (ProgramLauncherTests)
{
public:
TEST_METHOD (NormalWindow_ShouldUseExplorerShell)
{
Assert::IsTrue(KeyboardEventHandlers::ProgramLauncher::ShouldUseExplorerShell(Shortcut::StartWindowType::Normal));
}

TEST_METHOD (HiddenWindow_ShouldUseCreateProcess)
{
Assert::IsFalse(KeyboardEventHandlers::ProgramLauncher::ShouldUseExplorerShell(Shortcut::StartWindowType::Hidden));
}

TEST_METHOD (EmptyWorkingDirectory_ShouldUseExecutableDirectory)
{
const auto workingDirectory = KeyboardEventHandlers::ProgramLauncher::GetWorkingDirectory(
L"C:\\Program Files\\Example\\Example.exe",
L"");

Assert::AreEqual(L"C:\\Program Files\\Example", workingDirectory.c_str());
}

TEST_METHOD (ExecutableExtensionCheck_ShouldBeCaseInsensitive)
{
const auto workingDirectory = KeyboardEventHandlers::ProgramLauncher::GetWorkingDirectory(
L"C:\\Tools\\Example.EXE",
L"");

Assert::AreEqual(L"C:\\Tools", workingDirectory.c_str());
}

TEST_METHOD (ConfiguredWorkingDirectory_ShouldTakePrecedence)
{
const auto workingDirectory = KeyboardEventHandlers::ProgramLauncher::GetWorkingDirectory(
L"C:\\Program Files\\Example\\Example.exe",
L"D:\\Application Data");

Assert::AreEqual(L"D:\\Application Data", workingDirectory.c_str());
}

TEST_METHOD (ShellTargetWithoutWorkingDirectory_ShouldRemainEmpty)
{
const auto workingDirectory = KeyboardEventHandlers::ProgramLauncher::GetWorkingDirectory(
L"C:\\Shortcuts\\Example.lnk",
L"");

Assert::IsTrue(workingDirectory.empty());
}
};
}