Jump to content

Multi-Monitor Window Snap [beta]


jaberwacky
 Share

Recommended Posts

This is my first working example of a script that I've been wanting for a while.  I like the Windows Snap feature but I dislike that I have to switch from my mouse to my keyboard to use the feature for where my two monitors meet.  So, I have hacked up this first impression.  Please do try it out and tell me what you think!

Yes, requires one of the latest AutoIt3 betas that supports the latest syntax additions.

#AutoIt3Wrapper_Run_AU3Check=n
#AutoIt3Wrapper_AU3Check_Parameters=-w 1 -w 2 -w 3 -w 4 -w 6 -w 7 -d -q

#region ; Multi Monitor Window Snap
#region ; Includes
#include <APISysConstants.au3>

#include <GUIMenu.au3>

#include <Array.au3>

#include <GDIPlus.au3>

#include <WinAPIProc.au3>

#include <WinAPIGdi.au3>

#include <WinAPISys.au3>

#include <WindowsConstants.au3>

#include <BorderConstants.au3>

#include <WinAPIsysinfoConstants.au3>

#include <WindowsConstants.au3>
#EndRegion

#region ; Globals
Const $monitor_count = _WinAPI_GetSystemMetrics($SM_CMONITORS)

Const $monitor_height = _get_monitor_heights($monitor_count)

Const $monitor_width = _get_monitor_widths($monitor_count)

Const $desktop_width = _WinAPI_GetSystemMetrics($SM_CXVIRTUALSCREEN)

Const $tallest_monitor = _ArrayMax($monitor_height)

Const $desktop_left = _get_desktop_rect()[0]

Const $half_desktop_width = $desktop_width / 2

Const $hEventProc = DllCallbackRegister(_event_proc, "none", "ptr;dword;hwnd;long;long;dword;dword")

Const $hEventHook = _WinAPI_SetWinEventHook($EVENT_SYSTEM_MOVESIZESTART, $EVENT_SYSTEM_MOVESIZEEND, DllCallbackGetPtr($hEventProc))

Const $user32_dll = DllOpen("User32.dll")

Const $marker_board = GUICreate('', $desktop_width, $tallest_monitor, 0, 0, $WS_POPUP, BitOR($WS_EX_LAYERED, $WS_EX_TOPMOST, $WS_EX_TRANSPARENT))

GUISetState(@SW_SHOWNORMAL, $marker_board)

Global $DesktopDC = _WinAPI_GetDC($marker_board)
Global $DC        = _WinAPI_CreateCompatibleDC($DesktopDC)
Global $Bitmap    = _WinAPI_CreateCompatibleBitmap($DesktopDC, $desktop_width, $tallest_monitor)
 
_WinAPI_SelectObject($DC, $Bitmap)  

_GDIPlus_Startup()

Global $Graphics  = _GDIPlus_GraphicsCreateFromHDC($DC)
Global $Pen       = _GDIPlus_PenCreate(0xFFFFFFFF, 3)

Const $mouse_proc_callback = DllCallbackRegister(_ll_mouse_proc, "long", "int;wparam;lparam")

Const $hook = _WinAPI_SetWindowsHookEx($WH_MOUSE_LL, DllCallbackGetPtr($mouse_proc_callback), _WinAPI_GetModuleHandle(0))

OnAutoItExitRegister(_on_exit)

Const $range = 10

Global $window_focus, $left_snap, $right_snap, $active = False
#EndRegion

Do
  Sleep(1000)
Until False

#region ; Functions
Volatile Func _ll_mouse_proc($code, $w_param, $l_param)
  Switch $code >= 0
    Case True
      Switch $w_param
        Case $wm_mousemove
          If $active Then
            Local Const $mouse_pos = MouseGetPos()
            
            Select
              Case __WinAPI_PtInRectEx($mouse_pos, ($monitor_width[1] - $range), 0, $range, $monitor_height[1])
                Local Const $left   = ($monitor_width[1] + $desktop_left) / 2
                Local Const $top    = 0
                Local Const $width  = ($monitor_width[1] - $desktop_left) / 2
                Local Const $height = $monitor_height[1]
                
                DrawRectangle($left, $top, $width, $height)
                
                $left_snap = True
              
              Case __WinAPI_PtInRectEx($mouse_pos, $monitor_width[1], 0, $range, $monitor_height[2])
                Local Const $left   = $monitor_width[1]
                Local Const $top    = 0
                Local Const $width  = $monitor_width[2] / 2
                Local Const $height = $monitor_height[2]
                
                DrawRectangle($left, $top, $width, $height)
                
                ConsoleWrite("Left: "   & $left   & @CRLF & _
                             "Top: "    & $top    & @CRLF & _
                             "Width: "  & $width  & @CRLF & _
                             "Height: " & $height & @CRLF & @CRLF)
                             
                $right_snap = True   
                
              Case Else
                Select
                  Case $left_snap Or $right_snap
                    ClearRectangle()
                    
                    $left_snap  = False     
                    
                    $right_snap = False
                EndSelect
            EndSelect
          EndIf
          
        Case $wm_lbuttondown          
          $window_focus = _WinAPI_GetForegroundWindow()
      EndSwitch
  EndSwitch

  Return _WinAPI_CallNextHookEx($hook, $code, $w_param, $l_param)
EndFunc

Volatile Func _event_proc($hEventHook, $iEvent, $hWnd, $iObjectID, $iChildID, $iThreadId, $iEventTime)
    Switch $iEvent
      Case $EVENT_SYSTEM_MOVESIZESTART
        $active = True
      
      Case $EVENT_SYSTEM_MOVESIZEEND      
        If $active Then
          Select
            Case $left_snap
              ClearRectangle()
              
              Local Const $left   = ($monitor_width[1] + $desktop_left) / 2
              Local Const $top    = 0
              Local Const $width  = ($monitor_width[1] - $desktop_left) / 2
              Local Const $height = $monitor_height[1]
              
              WinMove($window_focus, "", $left, $top, $width, $height)
              
            Case $right_snap
              ClearRectangle()
              
              Local Const $left   = $monitor_width[1]
              Local Const $top    = 0
              Local Const $width  = $monitor_width[2] / 2
              Local Const $height = $monitor_height[2]
              
              WinMove($window_focus, "", $left, $top, $width, $height)
          EndSelect
          
          $left_snap  = False          
          $right_snap = False
          
          $window_focus = Null
        
          $active = False
        EndIf
    EndSwitch
EndFunc

Func __WinAPI_PtInRectEx(Const $pos, Const $left, Const $top, Const $width, Const $height)
  Local Static $rect = DllStructCreate($tagRECT)

  With $rect
    .Left   = $left
    .Top    = $top
    .Right  = $left + $width
    .Bottom = $top + $height
  EndWith

  Local Static $point = DllStructCreate($tagPOINT)

  With $point
    .X = $pos[0]
    .Y = $pos[1]
  EndWith

  Return DllCall($user32_dll, "bool", "PtInRect", "struct*", $rect, "struct", $point)[0]
EndFunc

Func _get_desktop_rect()
  Local Const $tWorkArea = DllStructCreate($tagRECT)
 
  _WinAPI_SystemParametersInfo($SPI_GETWORKAREA, 0, DllStructGetPtr($tWorkArea))
 
  Local $rect[4]
 
  $rect[0] = DllStructGetData($tWorkArea, "Left")
  $rect[1] = DllStructGetData($tWorkArea, "Top")
  $rect[2] = DllStructGetData($tWorkArea, "Right") - $rect[0]
  $rect[3] = DllStructGetData($tWorkArea, "Bottom") - $rect[1]
 
  Return $rect
EndFunc

Func _get_real_parent(Const $hwnd)
  ; http://stackoverflow.com/questions/16872126/the-correct-way-of-getting-the-parent-window
 
  Local Const $hParent = _WinAPI_GetAncestor($hwnd, $GA_PARENT)
 
  If (Not $hParent) Or ($hParent = _WinAPI_GetDesktopWindow()) Then
    Return True
  EndIf

  Return False
EndFunc

Func _get_monitor_heights(Const $count)  
  Local $height[($count + 1)]
 
  $height[0] = $count
 
  For $i = 1 To $height[0]
    $height[$i] = _get_monitor_info($i).Bottom
  Next
 
  Return $height
EndFunc

Func _get_monitor_widths(Const $count)  
  Local $width[($count + 1)]
 
  $width[0] = $count
 
  For $i = 1 To $width[0]    
    $width[$i] = _get_monitor_info($i).Right - _get_monitor_info($i).Left
  Next
 
  Return $width
EndFunc

Func _get_monitor_info(Const $monitor_number)
  If $monitor_number <= 0 Then
    Return SetError(1, 0, False)
  EndIf

  Local Const $monitor = _WinAPI_EnumDisplayMonitors()[$monitor_number][0]

  Return _WinAPI_GetMonitorInfo($monitor)[0]
EndFunc

Func DrawRectangle(Const $left, Const $top, Const $right, Const $bottom)  
  _GDIPlus_GraphicsDrawRect($Graphics, $left,  $top, $right, $bottom, $Pen)

  UpdateLayeredWindow()
EndFunc

Func ClearRectangle()
  _GDIPlus_GraphicsClear($Graphics, 0xFF000000)
 
  UpdateLayeredWindow()
EndFunc

Func UpdateLayeredWindow()
  Local Static $tSource = DllStructCreate($tagPOINT)

  Local Static $tSize = DllStructCreate($tagSIZE)    
  DllStructSetData($tSize, 1, $desktop_width)    
  DllStructSetData($tSize, 2, $tallest_monitor)

  Local Static $tBlend = DllStructCreate($tagBLENDFUNCTION)    
  DllStructSetData($tBlend, "Alpha", 255)    
  DllStructSetData($tBlend, "Format", 1)  

  DllCall($user32_dll,  "bool", "UpdateLayeredWindow", _
                        "hwnd",    $marker_board,      _
                        "handle",  $DesktopDC,         _
                        "ptr",     0,                  _
                        "struct*", $tSize,             _
                        "handle",  $DC,                _
                        "struct*", $tSource,           _
                        "dword",   0,                  _
                        "struct*", $tBlend,            _
                        "dword",   $ULW_ALPHA)
EndFunc

Volatile Func _on_exit()
  _WinAPI_UnhookWinEvent($hEventHook)
 
  _GDIPlus_PenDispose($Pen)
  _GDIPlus_GraphicsDispose($Graphics)
  _WinAPI_DeleteObject($Bitmap)
  _WinAPI_DeleteDC($DC)
  _GDIPlus_Shutdown()
 
  DllClose($user32_dll)
 
  DllCallbackFree($hEventProc)
 
  DllCallbackFree($mouse_proc_callback)
EndFunc
#EndRegion
Edited by jaberwacky
Link to comment
Share on other sites

I don't know yet how to test the code but I like the "style of coding" you have done here! I'm an oo noob and this code gives me some knowledge how to create objects. 

Maybe you can describe what to do to test it. Might be also nice to have some comments on the oo functions why you did it that way.

 

Br,

UEZ

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

To test this, first only two monitors are supported so far.  Also, only the left monitor has the snap feature with a sensible snap behavior.  But to test this, just take a window and hover it about ten pixels from the right edge of your first monitor.  You should see a white rectangle outlining the area where that window would snap to if you were to release the mouse.

So, gosh, these compliments mean a lot to me especially coming from you!  Thank you.

So, the way I use objects, and pretty much the best way to use them in AutoIt as far as I can tell though AutoItObject does support inheriting behaviors from other objects but not on the same level as c++ etc.  I use them to make black boxes.  The official UDFs have functions which are not meant to be called by ordinary users.  These are denoted by two leading underscores.  In an object, these functions could be declared "private" which autoitobject would then not allow the user to call, though you still could if you really wanted to. 

So, I think of a car as a black box.  There are variables and functions that the car needs to operate but the driver only needs to have access to a few of them: steering wheel, accelerator pedal, and brake pedal.  The private functions would be how much fuel each fuel injector receives based on how much air the engine is given.  Or how many times per second to actuate the brake calipers by the ABS system in the event that the brake pedal were slammed by the driver.

I could go on.  Any questions, ask though I'm no expert.

Edit: in my experience, GDI does not always lend itself well to autoitobject.  Though you're far more knowledgeable about GDI so you might see ways that I haven't.

Edited by jaberwacky
Link to comment
Share on other sites

UEZ, I decided to autoitobjectify one of your scripts.  This may not be the best example ever but it should give you a basic idea to go on.

;coded by UEZ 2009-01-09 - thanks to smashly for _GDIPlus_BrushSetSolidColor() function :-)

#AutoIt3Wrapper_Run_Obfuscator=y
#Obfuscator_Parameters=/sf /sv /om /cs=0 /cn=0
#AutoIt3Wrapper_UseUpx=n
;~ #AutoIt3Wrapper_Run_After=upx.exe --best "%out%"
#AutoIt3Wrapper_Run_After=upx.exe --ultra-brute "%out%"
#AutoIt3Wrapper_Run_After=del "Flying Pearl Necklaces_Obfuscated.au3"

#include <GuiConstantsEx.au3>

#include <GDIPlus.au3>

#include "AutoItObject.au3"

Opt('MustDeclareVars', 1)

Opt("GUIOnEventMode", 1)

_main()

Func _main()
  Local Const $Pi = 4 * ATan(1)

  Local Const $pi_div_90 = $Pi / 90

  Local Const $pi_div_120 = $Pi / 120

  Local Const $pi_div_75 = $Pi / 75

  Local Const $width = 400

  Local Const $height = $width

  Local $k, $xcoord1, $ycoord1, $xcoord2, $ycoord2, $size, $red, $green, $blue

  Local $GUI = GUICreate("GDI+: Flying Pearl Necklaces by UEZ 2009", $width, $height)
    
  GUISetOnEvent($GUI_EVENT_CLOSE, _close)

  GUISetState(@SW_SHOW)

  _AutoItObject_Startup()

  Local $oGDI = _oGDI()
 
  $oGDI.Init($GUI, $width, $height)

  Local $i = -650

  Local $l = $i

  Local $starting_point = 0

  Local $min_size = 13

  Local $i_p_j, $i_m_j, $h2, $w2, $sin_sp, $cos_sp

  Do
    $oGDI.GraphicsClear() ; clear buffer
    
    $k = 4096 ; 2^12
    
    $starting_point -= 0.05
    
    For $j = 0 To $k Step 32
      $i_p_j = $i + $j
      
      $red = ((Sin($i_p_j * 0.001953125) + 1) * 0.5) * 256 ; 2/1024=0.001953125
      
      $green = ((Sin($i_p_j * 0.0078125) + 1) * 0.5) * 256 ; 4/512=0.0078125
      
      $blue = ((Sin($i_p_j * 0.03125) + 1) * 0.5) * 256 ; 8/256=0.03125
      
      $oGDI.Brush1SetSolidColor($red, $green, $blue)
      
      $oGDI.Brush2SetSolidColor($red, $green, $blue)
      
      $size = $i - $j
      
      If $size > -$min_size And $size < $min_size Then
        $size = $min_size
      EndIf
      
      $i_m_j = $i - $j
      
      $sin_sp = Sin($starting_point)
      
      $cos_sp = Cos($starting_point)
      
      $h2 = $height / 2
      
      $w2 = $width / 2
      
      $xcoord1 = $w2 - ($i_m_j * 0.5) + $sin_sp * -Sin($i_m_j * $pi_div_90) * 64
      
      $ycoord1 = $h2 - ($i_m_j * 0.5) + -$cos_sp * Cos($i_m_j * $pi_div_90) * 32
      
      $oGDI.GraphicsFillEllipse1($xcoord1, $ycoord1, $size)
      
      $xcoord2 = $w2 - (-$i_m_j / -1.75) - $sin_sp * Sin($i_m_j * $pi_div_120) * 32
      
      $ycoord2 = $h2 - ($i_m_j / -1.75) - $cos_sp * Cos($i_m_j * $pi_div_75) * 16
      
      $oGDI.GraphicsFillEllipse2($xcoord2, $ycoord2, $size)
    Next
    
    $oGDI.GraphicsDrawImageRect(0, 0, $width, $height) ; copy to bitmap
    
    $i += 3
    
    If $i > $k + Abs($l) Then
      $i = $l
    EndIf
  Until Not Sleep(30)
EndFunc

Func _close()
  Exit
EndFunc

#region ; GDI Object
Func _oGDI()  
  Local $this = _AutoItObject_Class()  
 
  $this.AddMethod("Init",                  "_oGDI_Init")
  $this.AddMethod("GraphicsClear",         "_oGDI_GraphicsClear")
  $this.AddMethod("Brush1SetSolidColor",   "_oGDI_Brush1SetSolidColor")
  $this.AddMethod("Brush2SetSolidColor",   "_oGDI_Brush2SetSolidColor")
  $this.AddMethod("GraphicsFillEllipse1",  "_oGDI_GraphicsFillEllipse1")
  $this.AddMethod("GraphicsFillEllipse2",  "_oGDI_GraphicsFillEllipse2")
  $this.AddMethod("GraphicsDrawImageRect", "_oGDI_GraphicsDrawImageRect")
 
  $this.AddProperty("Graphic",        $elscope_private, "")
  $this.AddProperty("ParticleBitmap", $elscope_private, "")
  $this.AddProperty("ParticleBuffer", $elscope_private, "")
  $this.AddProperty("Brush1",         $elscope_private, "")
  $this.AddProperty("Brush2",         $elscope_private, "")
 
  $this.AddDestructor("dtor", "_oGDI_dtor")
 
  Return $this.Object
EndFunc

Func _oGDI_Init($this, $gui, $width, $height)
  _GDIPlus_Startup()
 
  $this.Graphic = _GDIPlus_GraphicsCreateFromHWND($gui) ; create graphic

  $this.ParticleBitmap = _GDIPlus_BitmapCreateFromGraphics($width, $height, $this.Graphic) ; create bitmap

  $this.ParticleBuffer = _GDIPlus_ImageGetGraphicsContext($this.ParticleBitmap) ; create buffer

  _GDIPlus_GraphicsSetSmoothingMode($this.ParticleBuffer, 4) ; Antialiasing

  _GDIPlus_GraphicsClear($this.ParticleBuffer) ; clear buffer
 
  $this.Brush1 = _GDIPlus_BrushCreateSolid(0)
 
  $this.Brush2 = _GDIPlus_BrushCreateSolid(0)
EndFunc

Func _oGDI_GraphicsDrawImageRect($this, $x, $y, $w, $h)
  _GDIPlus_GraphicsDrawImageRect($this.Graphic, $this.ParticleBitmap, $x, $y, $w, $h)
EndFunc

Func _oGDI_GraphicsFillEllipse1($this, $x, $y, $s)
  _GDIPlus_GraphicsFillEllipse($this.ParticleBuffer, $x, $y, $s * 0.166666666, $s * 0.166666666, $this.Brush1)
EndFunc

Func _oGDI_GraphicsFillEllipse2($this, $x, $y, $s)
  _GDIPlus_GraphicsFillEllipse($this.ParticleBuffer, $x, $y, $s * 0.125, $s * 0.125, $this.Brush2)
EndFunc

Func _oGDI_Brush1SetSolidColor($this, $r, $g, $b)
  _GDIPlus_BrushSetSolidColor($this.Brush1, "0xCF" & Hex($r, 2) & Hex($g, 2) & Hex($b, 2))
EndFunc

Func _oGDI_Brush2SetSolidColor($this, $r, $g, $b)
  _GDIPlus_BrushSetSolidColor($this.Brush2, "0xCF" & Hex($b, 2) & Hex($r, 2) & Hex($g, 2))
EndFunc

Func _oGDI_GraphicsClear($this)
  _GDIPlus_GraphicsClear($this.ParticleBuffer, 0xFFFFFFFF)
EndFunc

Func _oGDI_dtor($this) ; Clean up resources
  _GDIPlus_BrushDispose($this.Brush1)
 
  _GDIPlus_BrushDispose($this.Brush2)
 
  _GDIPlus_GraphicsDispose ($this.Graphic)
 
  _GDIPlus_BitmapDispose($this.ParticleBitmap)
 
  _GDIPlus_GraphicsDispose($this.ParticleBuffer)
 
  _GDIPlus_Shutdown()
EndFunc
#EndRegion

edit: overlooked something, fixed now

Edited by jaberwacky
Link to comment
Share on other sites

I will test your script from post #1 at my office where I work with 3 screens. :)

Thank you very much for your oo GDI+ example and explanations. I will check out what you did and revert if I have questions.

 

Br,

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

It won't work on three monitors so far.  I will get back to it when I wake up.

I updated the autoitobject example to be more clear and straight forward, I hope.

Link to comment
Share on other sites

The GDI+ examples is much slower than the original code but for me it is important to learn oo programming using AutoIt (first).

Thx again.

Edit: the example doesn't work with 3.3.13.19 -> $this.AddDestructor("dtor", "_oGDI_dtor") <-  The requested action with this object has failed.

Br,

UEZ

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Latest version.  Bumped up to beta.  Still only works for two monitors currently. 

No longer requires AutoItObject.

Edited by jaberwacky
Link to comment
Share on other sites

Can you explain please why you used Volatile functions? It seems not to clear for me and others what the purpose of Volatile is and when you should use it!

I disabled one of my 3 monitors to test your script but I it is not working - no snapping. I'm using Win8.1.

Why did you change your style and switched to non oo?

Br,

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Volatile wouldn't be of any use in this script.  I put them there at the start of my scripting but now I can remove them.  But um, I understand that volatile is for functions that are callback functions so that they will be called and run in a separate "thread" so to speak and not interfere with a gui message pump.  I may be wrong on this.  Edit: I may not benefit by removing them.  Seeing as to how there are two functions I think I can and should keep them volatile.

Shouldn't need to disable your 3rd monitor.  But that is distressing to hear that it doesn't work on Win 8.1.

I went from OO because you're right, the GDI is faster outside of the object.  I only just used it at the beginning because I wanted to drop in a rectangle object and call it and just have a rectangle on the screen.  Now that I'm getting something more along the lines of what I want I went ahead and took the object syntax out so that it will run faster.

Edited by jaberwacky
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...