1+ using System . Runtime . InteropServices ;
12using System . ComponentModel ;
23using MouseTrap . Models ;
34using MouseTrap . Native ;
45
5-
66namespace MouseTrap . Service ;
77
88public class MouseBridgeService : IService {
99 private ScreenConfigCollection _screens ;
1010
11+ private bool _wasMouseDown = false ;
12+ private bool _suppressBridge = false ;
13+
14+ [ DllImport ( "user32.dll" ) ]
15+ private static extern short GetAsyncKeyState ( int vKey ) ;
16+
17+ [ DllImport ( "user32.dll" ) ]
18+ private static extern IntPtr WindowFromPoint ( Point pt ) ;
19+
20+ [ DllImport ( "user32.dll" ) ]
21+ private static extern IntPtr SendMessage ( IntPtr hWnd , int msg , IntPtr wParam , IntPtr lParam ) ;
22+
23+ [ DllImport ( "user32.dll" ) ]
24+ private static extern int GetClassName ( IntPtr hWnd , System . Text . StringBuilder lpClassName , int nMaxCount ) ;
25+
26+ [ DllImport ( "user32.dll" ) ]
27+ private static extern bool GetWindowRect ( IntPtr hWnd , out RECT lpRect ) ;
28+
29+ [ DllImport ( "user32.dll" ) ]
30+ private static extern bool GetCursorInfo ( ref CURSORINFO pci ) ;
31+
32+ [ DllImport ( "user32.dll" ) ]
33+ private static extern IntPtr LoadCursor ( IntPtr hInstance , int lpCursorName ) ;
34+
35+ [ StructLayout ( LayoutKind . Sequential ) ]
36+ private struct RECT
37+ {
38+ public int Left ;
39+ public int Top ;
40+ public int Right ;
41+ public int Bottom ;
42+ }
43+
44+ [ StructLayout ( LayoutKind . Sequential ) ]
45+ private struct CURSORINFO
46+ {
47+ public int cbSize ;
48+ public int flags ;
49+ public IntPtr hCursor ;
50+ public Point ptScreenPos ;
51+ }
52+
53+ private const int WM_NCHITTEST = 0x84 ;
54+ private const int HTVSCROLL = 7 ;
55+ private const int IDC_IBEAM = 32513 ;
56+
57+ private bool IsLeftMouseDown ( )
58+ {
59+ return ( GetAsyncKeyState ( 0x01 ) & 0x8000 ) != 0 ;
60+ }
61+
62+ private bool IsIBeamCursor ( )
63+ {
64+ var ci = new CURSORINFO ( ) ;
65+ ci . cbSize = Marshal . SizeOf ( ci ) ;
66+ if ( ! GetCursorInfo ( ref ci ) )
67+ return false ;
68+
69+ IntPtr ibeam = LoadCursor ( IntPtr . Zero , IDC_IBEAM ) ;
70+ return ibeam != IntPtr . Zero && ci . hCursor == ibeam ;
71+ }
72+
73+ private bool ShouldSuppressBridgeOnDrag ( Point pos )
74+ {
75+ var hwnd = WindowFromPoint ( pos ) ;
76+ if ( hwnd == IntPtr . Zero )
77+ return false ;
78+
79+ // 1. Check for native/classic scrollbar via hit test
80+ int lParam = ( pos . Y << 16 ) | ( pos . X & 0xFFFF ) ;
81+ var hitTest = ( int ) SendMessage ( hwnd , WM_NCHITTEST , IntPtr . Zero , ( IntPtr ) lParam ) ;
82+ if ( hitTest == HTVSCROLL )
83+ return true ;
84+
85+ // 2. Check window class - if it's a text control, suppress on any drag
86+ var className = new System . Text . StringBuilder ( 256 ) ;
87+ if ( GetClassName ( hwnd , className , className . Capacity ) > 0 )
88+ {
89+ string cn = className . ToString ( ) ;
90+ if ( cn == "Edit" ||
91+ cn . StartsWith ( "RichEdit" ) ||
92+ cn == "Scintilla" ||
93+ cn == "WindowsForms10.EDIT.app.0" ||
94+ cn == "Chrome_RenderWidgetHostHWND" ||
95+ cn == "MozillaWindowClass" )
96+ {
97+ return true ;
98+ }
99+ }
100+
101+ // 3. Right-edge heuristic for custom scrollbars
102+ if ( GetWindowRect ( hwnd , out RECT windowRect ) )
103+ {
104+ int distanceFromRight = windowRect . Right - pos . X ;
105+ if ( distanceFromRight < 50 && distanceFromRight > 0 )
106+ return true ;
107+ }
108+
109+ // 4. Cursor shape fallback — catches CMD, PuTTY, and anything else
110+ // with an I-beam that we didn't explicitly enumerate
111+ if ( IsIBeamCursor ( ) )
112+ return true ;
113+
114+ return false ;
115+ }
116+
11117 public MouseBridgeService ( )
12118 {
13119 _screens = ScreenConfigCollection . Load ( ) ;
@@ -52,18 +158,35 @@ public void OnExit()
52158 MouseTrapClear ( ) ;
53159 }
54160
55-
56161 private void Loop ( CancellationToken token )
57162 {
58163 while ( ! token . IsCancellationRequested ) {
59- // on win-logon etc..
60164 if ( ! Mouse . IsInputDesktop ( ) ) {
61165 MouseTrapClear ( ) ;
62166 Thread . Sleep ( 1 ) ;
63167 continue ;
64168 }
65169
66170 var position = GetPosition ( ) ;
171+ var isDown = IsLeftMouseDown ( ) ;
172+
173+ if ( isDown && ! _wasMouseDown )
174+ {
175+ _suppressBridge = ShouldSuppressBridgeOnDrag ( position ) ;
176+ }
177+
178+ if ( ! isDown )
179+ {
180+ _suppressBridge = false ;
181+ }
182+
183+ _wasMouseDown = isDown ;
184+
185+ if ( _suppressBridge )
186+ {
187+ Thread . Sleep ( 1 ) ;
188+ continue ;
189+ }
67190
68191 var current = _screens . FirstOrDefault ( _ => _ . Bounds . Contains ( position ) ) ;
69192 if ( current != null && current . HasBridges ) {
@@ -101,7 +224,6 @@ private void Loop(CancellationToken token)
101224 }
102225 }
103226
104-
105227 // ^
106228 hotspace = current . TopHotSpace ;
107229 if ( direction . HasFlag ( Direction . ToTop ) && hotspace . Contains ( position ) ) {
@@ -137,7 +259,6 @@ private void Loop(CancellationToken token)
137259 }
138260 }
139261
140-
141262 private Point GetPosition ( )
142263 {
143264 if ( ! Mouse . TryGetPosition ( out var pos ) ) {
@@ -147,7 +268,6 @@ private Point GetPosition()
147268 return pos ;
148269 }
149270
150-
151271 private int _posOldx ;
152272 private int _posOldy ;
153273
@@ -156,25 +276,21 @@ private Direction GetDirection(in Point pos)
156276 var ret = Direction . None ;
157277 if ( _posOldx < pos . X ) {
158278 _posOldx = pos . X ;
159-
160279 ret |= Direction . ToRight ;
161280 }
162281
163282 if ( _posOldx > pos . X ) {
164283 _posOldx = pos . X ;
165-
166284 ret |= Direction . ToLeft ;
167285 }
168286
169287 if ( _posOldy < pos . Y ) {
170288 _posOldy = pos . Y ;
171-
172289 ret |= Direction . ToBottom ;
173290 }
174291
175292 if ( _posOldy > pos . Y ) {
176293 _posOldy = pos . Y ;
177-
178294 ret |= Direction . ToTop ;
179295 }
180296
0 commit comments