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.
sdottaka committed Oct 8, 2024

Verified

This commit was signed with the committer’s verified signature.
tomusdrw Tomek Drwięga
1 parent 91bfd52 commit 5ddf723
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
@@ -49,6 +49,7 @@
#include "SyntaxColors.h"
#include "Shell.h"
#include "DirTravel.h"
#include "LowLevelMouseHook.h"
#include <numeric>
#include <functional>

@@ -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
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
@@ -64,6 +64,7 @@
#include "RegKey.h"
#include "Win_VersionHelper.h"
#include "BCMenu.h"
#include "LowLevelMouseHook.h"

#ifdef _DEBUG
#define new DEBUG_NEW
@@ -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))
@@ -561,6 +564,8 @@ void CMergeApp::OnAppAbout()
*/
int CMergeApp::ExitInstance()
{
CLowLevelMouseHook::UnhookMouseHook();

charsets_cleanup();

// Save registry keys if existing WinMerge.reg
2 changes: 2 additions & 0 deletions Src/Merge.vcxproj
Original file line number Diff line number Diff line change
@@ -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" />
@@ -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" />
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.