Skip to content

Commit

Permalink
Add One-Hand mice wheel scroll diff and merge (#2435) (5). Alt+Wheel …
Browse files Browse the repository at this point in the history
…Down and other commands now work not only in the editor but also in other windows.
  • Loading branch information
sdottaka committed Oct 8, 2024
1 parent 91bfd52 commit 5ddf723
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 132 deletions.
3 changes: 3 additions & 0 deletions Src/DirView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "SyntaxColors.h"
#include "Shell.h"
#include "DirTravel.h"
#include "LowLevelMouseHook.h"
#include <numeric>
#include <functional>

Expand Down Expand Up @@ -638,6 +639,8 @@ void CDirView::Redisplay()
*/
void CDirView::OnContextMenu(CWnd*, CPoint point)
{
if (CLowLevelMouseHook::IsRightWheelScrolling())
return;
if (GetListCtrl().GetItemCount() == 0)
return;
// Make sure window is active
Expand Down
188 changes: 188 additions & 0 deletions Src/LowLevelMouseHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include <StdAfx.h>
#include "LowLevelMouseHook.h"

void CALLBACK CLowLevelMouseHook::TimerProc(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR id, DWORD unnamedParam4HWND)
{
KillTimer(nullptr, id);
EndMenu();
m_bIgnoreRBUp = false;
}

LRESULT CALLBACK CLowLevelMouseHook::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0)
return CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);

if (wParam == WM_RBUTTONDOWN)
{
m_bRButtonDown = true;
}
else if (wParam == WM_RBUTTONUP)
{
m_bRButtonDown = false;
if (m_bIgnoreRBUp)
{
LRESULT result = CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);
SetTimer(nullptr, 0, USER_TIMER_MINIMUM, TimerProc);
return result;
}
}
else if (wParam == WM_MOUSEWHEEL)
{
MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
int zDelta = GET_WHEEL_DELTA_WPARAM(pMouseStruct->mouseData);
int nFlags = pMouseStruct->flags;

if (GetKeyState(VK_MENU) & 0x8000)
{
HWND hwndTarget = GetForegroundWindow();
// When hold Alt key, use nFlags to check MK_CONTROL MK_SHIFT holding got problem, Use GetKeyState() instead.
const auto bShiftDown = GetKeyState(VK_SHIFT) & 0x8000;
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool up, < 0 scrool down
if (zDelta > 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollUp as Alt+Ctrl+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2LNEXT, 0);
return 1;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollUp as Alt+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+ScrollUp as Alt+Up
PostMessage(hwndTarget, WM_COMMAND, ID_PREVDIFF, 0);
return 1;
}
}
else if (zDelta < 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollDown as Alt+Ctrl+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2RNEXT, 0);
return 1;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollDown as Alt+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+ScrollDown as Alt+Down
PostMessage(hwndTarget, WM_COMMAND, ID_NEXTDIFF, 0);
return 1;
}
}
}

// Hold mice right button for One-handed operation
if (m_bRButtonDown)
{
HWND hwndTarget = GetForegroundWindow();
if (zDelta > 0)
{
// RButton+ScrollUp as Alt+Up
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_PREVDIFF, 0);
return 1;
}
else if (zDelta < 0)
{
// RButton+ScrollDown as Alt+Down
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_NEXTDIFF, 0);
return 1;
}
}
}
else if (wParam == WM_MOUSEHWHEEL)
{
MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
int zDelta = GET_WHEEL_DELTA_WPARAM(pMouseStruct->mouseData);
int nFlags = pMouseStruct->flags;

if (GetKeyState(VK_MENU) & 0x8000)
{
HWND hwndTarget = GetForegroundWindow();
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool right, < 0 scrool left
if (zDelta > 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollRight as Alt+Ctrl+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2RNEXT, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+HScrollRight as Alt+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
}
else if (zDelta < 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollLeft as Alt+Ctrl+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2LNEXT, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+HScrollLeft as Alt+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
}
}

// Hold mice right button for One-handed operation
if (m_bRButtonDown)
{
HWND hwndTarget = GetForegroundWindow();
if (zDelta > 0)
{
// RButton+ScrollRight as Alt+Right
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
else if (zDelta < 0)
{
// RButton+ScrollLeft as Alt+Left
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
}
}
return CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);
}

void CLowLevelMouseHook::SetMouseHook()
{
m_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, nullptr, 0);
}

void CLowLevelMouseHook::UnhookMouseHook()
{
if (m_hMouseHook)
{
UnhookWindowsHookEx(m_hMouseHook);
m_hMouseHook = nullptr;
}
}

13 changes: 13 additions & 0 deletions Src/LowLevelMouseHook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CLowLevelMouseHook
{
public:
static void SetMouseHook();
static void UnhookMouseHook();
static bool IsRightWheelScrolling() { return m_bIgnoreRBUp; }
private:
static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
static void CALLBACK TimerProc(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR id, DWORD unnamedParam4HWND);
inline static HHOOK m_hMouseHook;
inline static bool m_bIgnoreRBUp;
inline static bool m_bRButtonDown;
};
5 changes: 5 additions & 0 deletions Src/Merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#include "RegKey.h"
#include "Win_VersionHelper.h"
#include "BCMenu.h"
#include "LowLevelMouseHook.h"

#ifdef _DEBUG
#define new DEBUG_NEW
Expand Down Expand Up @@ -423,6 +424,8 @@ BOOL CMergeApp::InitInstance()
return FALSE;
}

CLowLevelMouseHook::SetMouseHook();

// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
Expand Down Expand Up @@ -561,6 +564,8 @@ void CMergeApp::OnAppAbout()
*/
int CMergeApp::ExitInstance()
{
CLowLevelMouseHook::UnhookMouseHook();

charsets_cleanup();

// Save registry keys if existing WinMerge.reg
Expand Down
2 changes: 2 additions & 0 deletions Src/Merge.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName)2.pch</PrecompiledHeaderOutputFile>
</ClCompile>
<ClCompile Include="LowLevelMouseHook.cpp" />
<ClCompile Include="MergeDocDiffCopy.cpp" />
<ClCompile Include="MyReBar.cpp" />
<ClCompile Include="PropCompareWebPage.cpp" />
Expand Down Expand Up @@ -1447,6 +1448,7 @@
<ClInclude Include="AboutDlg.h" />
<ClInclude Include="BasicFlatStatusBar.h" />
<ClInclude Include="ClipboardHistory.h" />
<ClInclude Include="LowLevelMouseHook.h" />
<ClInclude Include="MenuBar.h" />
<ClInclude Include="Common\cio.h" />
<ClInclude Include="Common\DebugNew.h" />
Expand Down
Loading

3 comments on commit 5ddf723

@lededev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the Windows file explorer right-click menu to open two WinMerge comparison windows. Then, when the right button of the second window is pressed, the scroll wheel is operated, and a menu will pop up when the right button is released.

The first comparison window opened can normally block this right button release action.

@lededev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is not to use the WH_MOUSE_LL hook. Using a global hook may cause many side effects. For each MFC window that needs this function, you can handle pMsg->message == WM_MOUSEWHEEL and pMsg->message == WM_MOUSEHWHEEL in advance in PreTranslateMessage(pMsg).

@sdottaka
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently in an environment without a mouse so I can't test it, but it's not good to have it be a global hook, so I changed it to use WH_MOUSE instead of WH_MOUSE_LL.

Please sign in to comment.