diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..fcb225f --- /dev/null +++ b/Main.cpp @@ -0,0 +1,217 @@ +// Main.cpp +// UniversalPauseButton +// Ryan Ries, 2015 +// ryan@myotherpcisacloud.com +// +// Must compile in Unicode. + +#include +#include +#include +#include "resource.h" + +#define WM_TRAYICON (WM_USER + 1) + +typedef LONG(NTAPI* _NtSuspendProcess) (IN HANDLE ProcessHandle); +typedef LONG(NTAPI* _NtResumeProcess) (IN HANDLE ProcessHandle); + +_NtSuspendProcess NtSuspendProcess = (_NtSuspendProcess)GetProcAddress(GetModuleHandle(L"ntdll"), "NtSuspendProcess"); +_NtResumeProcess NtResumeProcess = (_NtResumeProcess)GetProcAddress(GetModuleHandle(L"ntdll"), "NtResumeProcess"); + +NOTIFYICONDATA G_TrayNotifyIconData; +HANDLE G_Mutex; + + +// The WindowProc (callback) for WinMain's WindowClass. +// Basically the system tray does nothing except lets the user know that it's running. +// If the user clicks the tray icon it will ask if they want to exit the app. +LRESULT CALLBACK WindowClassCallback(_In_ HWND Window, _In_ UINT Message, _In_ WPARAM WParam, _In_ LPARAM LParam) +{ + LRESULT Result = 0; + + switch (Message) + { + case WM_TRAYICON: + { + if (LParam == WM_LBUTTONDOWN || LParam == WM_RBUTTONDOWN) + { + if (MessageBox(Window, L"Quit UniversalPauseButton?", L"Are you sure?", MB_YESNO | MB_ICONQUESTION) == IDYES) + { + Shell_NotifyIcon(NIM_DELETE, &G_TrayNotifyIconData); + PostQuitMessage(0); + } + } + } + default: + { + Result = DefWindowProc(Window, Message, WParam, LParam); + break; + } + } + return(Result); +} + +// Entry point. +int CALLBACK WinMain(_In_ HINSTANCE Instance, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int) +{ + G_Mutex = CreateMutex(NULL, FALSE, L"UniversalPauseButton"); + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + MessageBox(NULL, L"An instance of the program is already running.", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + return(ERROR_ALREADY_EXISTS); + } + + WNDCLASS SysTrayWindowClass = { 0 }; + + SysTrayWindowClass.style = CS_HREDRAW | CS_VREDRAW; + SysTrayWindowClass.hInstance = Instance; + SysTrayWindowClass.lpszClassName = L"UniversalPauseButton_Systray_WindowClass"; + SysTrayWindowClass.hbrBackground = CreateSolidBrush(RGB(255, 0, 255)); + SysTrayWindowClass.lpfnWndProc = WindowClassCallback; + + if (RegisterClass(&SysTrayWindowClass) == 0) + { + MessageBox(NULL, L"Failed to register WindowClass!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + return(E_FAIL); + } + + HWND SystrayWindow = CreateWindowEx( + WS_EX_TOOLWINDOW, + SysTrayWindowClass.lpszClassName, + L"UniversalPauseButton_Systray_Window", + WS_ICONIC, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + 0, + 0, + Instance, + 0); + + if (SystrayWindow == 0) + { + MessageBox(NULL, L"Failed to create SystrayWindow!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + return(E_FAIL); + } + + G_TrayNotifyIconData.cbSize = sizeof(NOTIFYICONDATA); + G_TrayNotifyIconData.hWnd = SystrayWindow; + G_TrayNotifyIconData.uID = 1982; + G_TrayNotifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + G_TrayNotifyIconData.uCallbackMessage = WM_TRAYICON; + + wcscpy_s(G_TrayNotifyIconData.szTip, L"Universal Pause Button v1.0"); + + G_TrayNotifyIconData.hIcon = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, 0, 0, NULL); + + if (G_TrayNotifyIconData.hIcon == NULL) + { + MessageBox(NULL, L"Failed to load systray icon resource!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + return(E_FAIL); + } + + if (Shell_NotifyIcon(NIM_ADD, &G_TrayNotifyIconData) == FALSE) + { + MessageBox(NULL, L"Failed to register systray icon!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + return(E_FAIL); + } + + MSG SysTrayWindowMessage = { 0 }; + static int PauseKeyWasDown = 0; + DWORD PreviouslySuspendedProcessID = 0; + wchar_t PreviouslySuspendedProcessText[256] = { 0 }; + + while (SysTrayWindowMessage.message != WM_QUIT) + { + while (PeekMessage(&SysTrayWindowMessage, SystrayWindow, 0, 0, PM_REMOVE)) + { + DispatchMessage(&SysTrayWindowMessage); + } + + int PauseKeyIsDown = GetAsyncKeyState(VK_PAUSE); + + if (PauseKeyIsDown && !PauseKeyWasDown) + { + HWND ForegroundWindow = GetForegroundWindow(); + if (ForegroundWindow) + { + DWORD ProcessID = 0; + GetWindowThreadProcessId(ForegroundWindow, &ProcessID); + if (ProcessID != 0) + { + HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID); + if (ProcessHandle != 0) + { + if (PreviouslySuspendedProcessID == 0) + { + NtSuspendProcess(ProcessHandle); + PreviouslySuspendedProcessID = ProcessID; + GetWindowText(ForegroundWindow, PreviouslySuspendedProcessText, sizeof(PreviouslySuspendedProcessText) / sizeof(wchar_t)); + } + else if (PreviouslySuspendedProcessID == ProcessID) + { + NtResumeProcess(ProcessHandle); + PreviouslySuspendedProcessID = 0; + memset(PreviouslySuspendedProcessText, 0, sizeof(PreviouslySuspendedProcessText)); + } + else + { + // The user pressed the pause button while focused on another process than what was + // originally paused and the first process is still paused. + DWORD AllProcesses[2048] = { 0 }; + DWORD BytesReturned = 0; + BOOL PreviouslySuspendedProcessIsStillRunning = FALSE; + + if (EnumProcesses(AllProcesses, sizeof(AllProcesses), &BytesReturned) != 0) + { + for (DWORD Counter = 0; Counter < (BytesReturned / sizeof(DWORD)); Counter++) + { + if ((AllProcesses[Counter] != 0) && AllProcesses[Counter] == PreviouslySuspendedProcessID) + { + PreviouslySuspendedProcessIsStillRunning = TRUE; + } + } + if (PreviouslySuspendedProcessIsStillRunning) + { + wchar_t MessageBoxBuffer[1024] = { 0 }; + _snwprintf_s(MessageBoxBuffer, sizeof(MessageBoxBuffer), L"You must first unpause %s (PID %d) before pausing another program.", PreviouslySuspendedProcessText, PreviouslySuspendedProcessID); + MessageBox(ForegroundWindow, MessageBoxBuffer, L"Universal Pause Button", MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL); + } + else + { + // The paused process is no more, so reset. + PreviouslySuspendedProcessID = 0; + memset(PreviouslySuspendedProcessText, 0, sizeof(PreviouslySuspendedProcessText)); + } + } + else + { + MessageBox(NULL, L"EnumProcesses failed!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + } + } + CloseHandle(ProcessHandle); + } + else + { + MessageBox(NULL, L"OpenProcess failed!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + } + } + else + { + MessageBox(NULL, L"Unable to get process ID of foreground window!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + } + } + else + { + MessageBox(NULL, L"Unable to detect foreground window!", L"UniversalPauseButton Error", MB_OK | MB_ICONERROR); + } + } + + PauseKeyWasDown = PauseKeyIsDown; + + Sleep(10); + } + + return(S_OK); +} \ No newline at end of file diff --git a/UniversalPauseButton.rc b/UniversalPauseButton.rc new file mode 100644 index 0000000..1509512 Binary files /dev/null and b/UniversalPauseButton.rc differ diff --git a/UniversalPauseButton.sln b/UniversalPauseButton.sln new file mode 100644 index 0000000..e45b56e --- /dev/null +++ b/UniversalPauseButton.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UniversalPauseButton", "UniversalPauseButton.vcxproj", "{229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Debug|Win32.ActiveCfg = Debug|Win32 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Debug|Win32.Build.0 = Debug|Win32 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Debug|x64.ActiveCfg = Debug|x64 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Debug|x64.Build.0 = Debug|x64 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Release|Win32.ActiveCfg = Release|Win32 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Release|Win32.Build.0 = Release|Win32 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Release|x64.ActiveCfg = Release|x64 + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/UniversalPauseButton.vcxproj b/UniversalPauseButton.vcxproj new file mode 100644 index 0000000..32a3a4c --- /dev/null +++ b/UniversalPauseButton.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {229438CB-2B5E-4C34-B1CE-F9AC14B0C47C} + UniversalPauseButton + + + + Application + true + v120 + Unicode + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\Temp\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\Temp\ + + + $(SolutionDir)$(Platform)\$(Configuration)\Temp\ + + + $(SolutionDir)$(Platform)\$(Configuration)\Temp\ + + + + Level4 + Disabled + true + MultiThreadedDebug + + + true + + + + + Level4 + Disabled + true + MultiThreadedDebug + + + true + + + + + Level4 + MaxSpeed + true + true + true + MultiThreaded + + + true + true + true + + + + + Level4 + MaxSpeed + true + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UniversalPauseButton.vcxproj.filters b/UniversalPauseButton.vcxproj.filters new file mode 100644 index 0000000..dd4f00f --- /dev/null +++ b/UniversalPauseButton.vcxproj.filters @@ -0,0 +1,37 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/pause.ico b/pause.ico new file mode 100644 index 0000000..6d793b1 Binary files /dev/null and b/pause.ico differ diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..c3918a5 Binary files /dev/null and b/resource.h differ