@@ -225,17 +225,16 @@ static async Task MaximizeWindow(Process process)
225225
226226 // Wait briefly for the window to finish layout after maximize
227227 await Task . Delay ( 500 ) ;
228- CenterVerticalSplit ( process . MainWindowHandle ) ;
228+ CenterSplits ( process . MainWindowHandle ) ;
229229 return ;
230230 }
231231
232232 await Task . Delay ( 100 ) ;
233233 }
234234 }
235235
236- static void CenterVerticalSplit ( IntPtr mainWindow )
236+ static void CenterSplits ( IntPtr mainWindow )
237237 {
238- // Collect all child windows with their parent, class name, and rect
239238 var children = new List < ( IntPtr Handle , IntPtr Parent , string ClassName , RECT Rect ) > ( ) ;
240239 EnumChildWindows ( mainWindow , ( hwnd , _ ) =>
241240 {
@@ -245,8 +244,7 @@ static void CenterVerticalSplit(IntPtr mainWindow)
245244 return true ;
246245 } , IntPtr . Zero ) ;
247246
248- // Log child window hierarchy for diagnostics
249- Log . Information ( "CenterVerticalSplit: found {Count} child windows" , children . Count ) ;
247+ Log . Information ( "CenterSplits: found {Count} child windows" , children . Count ) ;
250248 foreach ( var child in children )
251249 {
252250 var w = child . Rect . Right - child . Rect . Left ;
@@ -257,12 +255,21 @@ static void CenterVerticalSplit(IntPtr mainWindow)
257255 child . Rect . Left , child . Rect . Top , w , h ) ;
258256 }
259257
260- // Find the vertical splitter: look for pairs of side-by-side siblings
261- // with similar height that together span most of their parent's width.
262- // Pick the pair with the largest combined area.
258+ CenterSplit ( children , SplitOrientation . Vertical ) ;
259+ CenterSplit ( children , SplitOrientation . Horizontal ) ;
260+ }
261+
262+ enum SplitOrientation
263+ {
264+ Vertical ,
265+ Horizontal
266+ }
267+
268+ static void CenterSplit ( List < ( IntPtr Handle , IntPtr Parent , string ClassName , RECT Rect ) > children , SplitOrientation orientation )
269+ {
263270 var bestArea = 0 ;
264- var bestLeftRect = default ( RECT ) ;
265- var bestRightRect = default ( RECT ) ;
271+ var bestFirstRect = default ( RECT ) ;
272+ var bestSecondRect = default ( RECT ) ;
266273 var bestParent = IntPtr . Zero ;
267274
268275 foreach ( var group in children . GroupBy ( c => c . Parent ) )
@@ -275,88 +282,125 @@ static void CenterVerticalSplit(IntPtr mainWindow)
275282 {
276283 var a = siblings [ i ] ;
277284 var b = siblings [ j ] ;
278- var heightA = a . Rect . Bottom - a . Rect . Top ;
279- var heightB = b . Rect . Bottom - b . Rect . Top ;
280-
281285 var widthA = a . Rect . Right - a . Rect . Left ;
282286 var widthB = b . Rect . Right - b . Rect . Left ;
287+ var heightA = a . Rect . Bottom - a . Rect . Top ;
288+ var heightB = b . Rect . Bottom - b . Rect . Top ;
283289
284- if ( heightA < 100 || heightB < 100 ||
285- widthA <= 0 || widthB <= 0 )
290+ if ( widthA <= 0 || widthB <= 0 ||
291+ heightA <= 0 || heightB <= 0 )
286292 {
287293 continue ;
288294 }
289295
290- if ( Math . Abs ( heightA - heightB ) > 20 ||
291- Math . Abs ( a . Rect . Top - b . Rect . Top ) > 20 )
296+ GetClientRect ( group . Key , out var parentClient ) ;
297+
298+ bool isMatch ;
299+ if ( orientation == SplitOrientation . Vertical )
300+ {
301+ // Side-by-side: same height/top, span parent width
302+ isMatch = heightA >= 100 && heightB >= 100 &&
303+ Math . Abs ( heightA - heightB ) <= 20 &&
304+ Math . Abs ( a . Rect . Top - b . Rect . Top ) <= 20 &&
305+ Math . Max ( a . Rect . Right , b . Rect . Right ) - Math . Min ( a . Rect . Left , b . Rect . Left ) >= parentClient . Right * 0.8 ;
306+ }
307+ else
308+ {
309+ // Stacked: same width/left, span parent height
310+ isMatch = widthA >= 100 && widthB >= 100 &&
311+ Math . Abs ( widthA - widthB ) <= 20 &&
312+ Math . Abs ( a . Rect . Left - b . Rect . Left ) <= 20 &&
313+ Math . Max ( a . Rect . Bottom , b . Rect . Bottom ) - Math . Min ( a . Rect . Top , b . Rect . Top ) >= parentClient . Bottom * 0.8 ;
314+ }
315+
316+ if ( ! isMatch )
292317 {
293318 continue ;
294319 }
295320
296- GetClientRect ( group . Key , out var parentClient ) ;
297- var totalSpan = Math . Max ( a . Rect . Right , b . Rect . Right ) - Math . Min ( a . Rect . Left , b . Rect . Left ) ;
298- if ( totalSpan < parentClient . Right * 0.8 )
321+ // Require a gap between the panels (the splitter bar).
322+ // Adjacent windows without a gap (e.g. ribbon/content) are not splits.
323+ int gap ;
324+ if ( orientation == SplitOrientation . Vertical )
325+ {
326+ var left = a . Rect . Left < b . Rect . Left ? a . Rect : b . Rect ;
327+ var right = a . Rect . Left < b . Rect . Left ? b . Rect : a . Rect ;
328+ gap = right . Left - left . Right ;
329+ }
330+ else
331+ {
332+ var top = a . Rect . Top < b . Rect . Top ? a . Rect : b . Rect ;
333+ var bottom = a . Rect . Top < b . Rect . Top ? b . Rect : a . Rect ;
334+ gap = bottom . Top - top . Bottom ;
335+ }
336+
337+ if ( gap <= 0 )
299338 {
300339 continue ;
301340 }
302341
303- var area = ( a . Rect . Right - a . Rect . Left ) * heightA +
304- ( b . Rect . Right - b . Rect . Left ) * heightB ;
342+ var area = widthA * heightA + widthB * heightB ;
305343 if ( area <= bestArea )
306344 {
307345 continue ;
308346 }
309347
310348 bestArea = area ;
311349 bestParent = group . Key ;
312- if ( a . Rect . Left <= b . Rect . Left )
350+ if ( orientation == SplitOrientation . Vertical )
313351 {
314- bestLeftRect = a . Rect ;
315- bestRightRect = b . Rect ;
352+ bestFirstRect = a . Rect . Left <= b . Rect . Left ? a . Rect : b . Rect ;
353+ bestSecondRect = a . Rect . Left <= b . Rect . Left ? b . Rect : a . Rect ;
316354 }
317355 else
318356 {
319- bestLeftRect = b . Rect ;
320- bestRightRect = a . Rect ;
357+ bestFirstRect = a . Rect . Top <= b . Rect . Top ? a . Rect : b . Rect ;
358+ bestSecondRect = a . Rect . Top <= b . Rect . Top ? b . Rect : a . Rect ;
321359 }
322360 }
323361 }
324362 }
325363
326364 if ( bestArea == 0 )
327365 {
328- Log . Information ( "CenterVerticalSplit : no matching split panel pair found" ) ;
366+ Log . Information ( "CenterSplit({Orientation}) : no matching pair found" , orientation ) ;
329367 return ;
330368 }
331369
332- // The splitter bar sits in the gap between the two panels.
333- // Convert splitter screen position to parent client coordinates and
334- // send mouse messages directly to the parent (SplitContainer) window.
335- var splitterScreenX = ( bestLeftRect . Right + bestRightRect . Left ) / 2 ;
336- var splitterScreenY = ( bestLeftRect . Top + bestLeftRect . Bottom ) / 2 ;
370+ // Compute splitter position and target in screen coordinates
371+ GetWindowRect ( bestParent , out var parentRect ) ;
337372
338- var splitterPoint = new POINT { X = splitterScreenX , Y = splitterScreenY } ;
339- ScreenToClient ( bestParent , ref splitterPoint ) ;
340-
341- GetClientRect ( bestParent , out var client ) ;
342- var targetClientX = client . Right / 2 ;
373+ int fromX , fromY , toX , toY ;
374+ if ( orientation == SplitOrientation . Vertical )
375+ {
376+ fromX = ( bestFirstRect . Right + bestSecondRect . Left ) / 2 ;
377+ fromY = ( bestFirstRect . Top + bestFirstRect . Bottom ) / 2 ;
378+ toX = ( parentRect . Left + parentRect . Right ) / 2 ;
379+ toY = fromY ;
380+ }
381+ else
382+ {
383+ fromX = ( bestFirstRect . Left + bestFirstRect . Right ) / 2 ;
384+ fromY = ( bestFirstRect . Bottom + bestSecondRect . Top ) / 2 ;
385+ toX = fromX ;
386+ toY = ( parentRect . Top + parentRect . Bottom ) / 2 ;
387+ }
343388
344389 Log . Information (
345- "CenterVerticalSplit: sending drag from client ({FromX},{FromY}) to ({ToX},{ToY})" ,
346- splitterPoint . X , splitterPoint . Y , targetClientX , splitterPoint . Y ) ;
347-
348- var downLParam = MakeLParam ( splitterPoint . X , splitterPoint . Y ) ;
349- var moveLParam = MakeLParam ( targetClientX , splitterPoint . Y ) ;
350-
351- // WM_LBUTTONDOWN = 0x0201, WM_MOUSEMOVE = 0x0200, WM_LBUTTONUP = 0x0202
352- // MK_LBUTTON = 0x0001
353- SendMessage ( bestParent , 0x0201 , ( IntPtr ) 0x0001 , downLParam ) ;
354- SendMessage ( bestParent , 0x0200 , ( IntPtr ) 0x0001 , moveLParam ) ;
355- SendMessage ( bestParent , 0x0202 , IntPtr . Zero , moveLParam ) ;
390+ "CenterSplit({Orientation}): drag screen ({FromX},{FromY}) to ({ToX},{ToY})" ,
391+ orientation , fromX , fromY , toX , toY ) ;
392+
393+ SetCursorPos ( fromX , fromY ) ;
394+ Thread . Sleep ( 100 ) ;
395+ mouse_event ( MOUSEEVENTF_LEFTDOWN , 0 , 0 , 0 , IntPtr . Zero ) ;
396+ Thread . Sleep ( 100 ) ;
397+ SetCursorPos ( toX , toY ) ;
398+ Thread . Sleep ( 100 ) ;
399+ mouse_event ( MOUSEEVENTF_LEFTUP , 0 , 0 , 0 , IntPtr . Zero ) ;
356400 }
357401
358- static IntPtr MakeLParam ( int x , int y ) =>
359- ( IntPtr ) ( ( y << 16 ) | ( x & 0xFFFF ) ) ;
402+ const uint MOUSEEVENTF_LEFTDOWN = 0x0002 ;
403+ const uint MOUSEEVENTF_LEFTUP = 0x0004 ;
360404
361405 delegate bool EnumWindowsProc ( IntPtr hWnd , IntPtr lParam ) ;
362406
@@ -397,10 +441,10 @@ struct POINT
397441
398442 [ LibraryImport ( "user32.dll" ) ]
399443 [ return : MarshalAs ( UnmanagedType . Bool ) ]
400- private static partial bool ScreenToClient ( IntPtr hWnd , ref POINT lpPoint ) ;
444+ private static partial bool SetCursorPos ( int x , int y ) ;
401445
402- [ LibraryImport ( "user32.dll" , EntryPoint = "SendMessageW" ) ]
403- private static partial IntPtr SendMessage ( IntPtr hWnd , uint msg , IntPtr wParam , IntPtr lParam ) ;
446+ [ LibraryImport ( "user32.dll" ) ]
447+ private static partial void mouse_event ( uint dwFlags , int dx , int dy , uint dwData , IntPtr dwExtraInfo ) ;
404448
405449 static string GetWindowClassName ( IntPtr hWnd )
406450 {
0 commit comments