Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Flinging Windows Across a Multi-Monitor System


  • Please log in to reply
11 replies to this topic
PatrickS
  • Members
  • 14 posts
  • Last active: Jul 20 2019 01:08 AM
  • Joined: 21 Mar 2010
The function Win__Fling() listed below can be used to fling (i.e., move, shift) a window between monitors in a multi-monitor system.

I found this post from rodfell from June 2009 which offered up a neat piece of code to "throw" (what I'm calling "fling") a window between monitors. Inspired to understand what was going on, I developed my own version which you can find below.

Some of the features I like about this function are:
[*:2gvd4j4t]It offers easy ways to specify the window to throw / fling. You can tell it to fling the active window, or the window under the mouse (as in Rodfell's solution), or any window, based on its window ID.
[*:2gvd4j4t]It maintains a window's relative size across the fling. If the window used to occupy, say, the entire right half of the screen, it still will after changing monitors (assuming the window is able to be resized).
[*:2gvd4j4t]It handles maximized windows and retains the window's non-maximized shape across the fling.
[*:2gvd4j4t]It should work with any number of attached / active monitors [not tested].Here's the code.
;; -----------------------------------------------------------------------
;; Fling (or shift) a window from one monitor to the next in a multi-monitor system.
;;
;; Function Parameters:
;;
;;		FlingDirection		The direction of the fling, expected to be either +1 or -1.
;;							The function is not limited to just two monitors; it supports
;;							as many monitors as are currently connected to the system and
;;							can fling a window serially through each of them in turn.
;;
;;		WinID				The window ID of the window to move. There are two special WinID
;;							values supported:
;;
;;							1) The value "A" means to use the Active window (default).
;;							2) The value "M" means to use the window currently under the Mouse.
;;
;; The flinged window will be resized to have the same *relative* size in the new monitor.
;; For example, if the window originally occupied the entire right half of the screen,
;; it will again on the new monitor (assuming the window can be resized).
;;
;; Minimized windows are not modified; they are left exactly where they were.
;;
;; The return value of the function is non-zero if the window was successfully flung.
;;
;; Example hotkeys:
;;	#NumpadEnter::	Win__Fling(1, "A")	; Windows-NumpadEnter flings the active window
;;	#LButton::		Win__Fling(1, "M")	; Windows-LeftClick flings the window under the mouse
;;
;; Copyright (c) 2010 Patrick Sheppard
;; All Rights Reserved

Win__Fling(FlingDirection = 1, WinID = "A")
{
	; Figure out which window to move based on the "WinID" function parameter:
	;	1) The letter "A" means to use the Active window
	;	2) The letter "M" means to use the window under the Mouse
	; Otherwise, the parameter value is assumed to be the AHK window ID of the window to use.

	if (WinID = "A")
	{
		; If the user supplied an "A" as the window ID, we use the Active window
		WinID := WinExist("A")
	}
	else if (WinID = "M")
	{
		; If the user supplied an "M" as the window ID, we use the window currently under the Mouse
		MouseGetPos, MouseX, MouseY, WinID		; MouseX & MouseY are retrieved but, for now, not used
	}

	; Check to make sure we are working with a valid window
	IfWinNotExist, ahk_id %WinID%
	{
		; Make a short noise so the user knows to stop expecting something fun to happen.
		SoundPlay, *64
		
		; Debug Support
		;MsgBox, 16, Window Fling: Error, Specified window does not exist.`nWindow ID = %WinID%

		return 0
	}

	; Here's where we find out just how many monitors we're dealing with
	SysGet, MonitorCount, MonitorCount

	if (MonitorCount <= 1)
	{
		; Honestly, there's not much to do in a one-monitor system
		return 1
	}

	; For each active monitor, we get Top, Bottom, Left, Right of the monitor's
	;  'Work Area' (i.e., excluding taskbar, etc.). From these values we compute Width and Height.
	;  Results get put into variables named like "Monitor1Top" and "Monitor2Width", etc.,
	;  with the monitor number embedded in the middle of the variable name.

	Loop, %MonitorCount%
	{
		SysGet, Monitor%A_Index%, MonitorWorkArea, %A_Index%
		Monitor%A_Index%Width  := Monitor%A_Index%Right  - Monitor%A_Index%Left
		Monitor%A_Index%Height := Monitor%A_Index%Bottom - Monitor%A_Index%Top
	}

	; Retrieve the target window's original minimized / maximized state
	WinGet, WinOriginalMinMaxState, MinMax, ahk_id %WinID%

	; We don't do anything with minimized windows (for now... this may change)
	if (WinOriginalMinMaxState = -1)
	{
		; Debatable as to whether or not this should be flagged as an error
		return 0
	}
	
	; If the window started out maximized, then the plan is to:
	;	(a) restore it,
	;	(b) fling it, then
	;	(c) re-maximize it on the target monitor.
	;
	; The reason for this is so that the usual maximize / restore windows controls
	; work as you'd expect. You want Windows to use the dimensions of the non-maximized
	; window when you click the little restore icon on a previously flung (maximized) window.
	
	if (WinOriginalMinMaxState = 1)
	{
		; Restore a maximized window to its previous state / size ... before "flinging".
		;
		; Programming Note: It would be nice to hide the window before doing this ... 
		; the window does some visual calisthenics that the user may construe as a bug.
		; Unfortunately, if you hide a window then you can no longer work with it. <Sigh>

		WinRestore, ahk_id %WinID%
	}

	; Retrieve the target window's original (non-maximized) dimensions
	WinGetPos, WinX, WinY, WinW, WinH, ahk_id %WinID%

	; Find the point at the centre of the target window then use it
	; to determine the monitor to which the target window belongs
	; (windows don't have to be entirely contained inside any one monitor's area).
	
	WinCentreX := WinX + WinW / 2
	WinCentreY := WinY + WinH / 2

	CurrMonitor = 0
	Loop, %MonitorCount%
	{
		if (    (WinCentreX >= Monitor%A_Index%Left) and (WinCentreX < Monitor%A_Index%Right )
		    and (WinCentreY >= Monitor%A_Index%Top ) and (WinCentreY < Monitor%A_Index%Bottom))
		{
			CurrMonitor = %A_Index%
			break
		}
	}

	; Compute the number of the next monitor in the direction of the specified fling (+1 or -1)
	;  Valid monitor numbers are 1..MonitorCount, and we effect a circular fling.
	NextMonitor := CurrMonitor + FlingDirection
	if (NextMonitor > MonitorCount)
	{
		NextMonitor = 1
	}
	else if (NextMonitor <= 0)
	{
		NextMonitor = %MonitorCount%
	}

	; Scale the position / dimensions of the target window by the ratio of the monitor sizes.
	; Programming Note: Do multiplies before divides in order to maintain accuracy in the integer calculation.
	WinFlingX := (WinX - Monitor%CurrMonitor%Left) * Monitor%NextMonitor%Width  // Monitor%CurrMonitor%Width  + Monitor%NextMonitor%Left
	WinFlingY := (WinY - Monitor%CurrMonitor%Top ) * Monitor%NextMonitor%Height // Monitor%CurrMonitor%Height + Monitor%NextMonitor%Top
	WinFlingW :=  WinW							   * Monitor%NextMonitor%Width  // Monitor%CurrMonitor%Width
	WinFlingH :=  WinH							   * Monitor%NextMonitor%Height // Monitor%CurrMonitor%Height

	; It's time for the target window to make its big move
	WinMove, ahk_id %WinID%,, WinFlingX, WinFlingY, WinFlingW, WinFlingH

	; If the window used to be maximized, maximize it again on its new monitor
	if (WinOriginalMinMaxState = 1)
	{
		WinMaximize, ahk_id %WinID%
	}

	return 1
}
As you can see, it's pretty heavily commented, so you should be able to get in there and muck with it if it doesn't immediately suit. I've written it to work with any number of monitors, but I only have a two-monitor system so I can't really test this. I'd be happy to hear from others if it works.

The comments include two example hotkeys that you may want to consider:

#NumpadEnter::	Win__Fling(1, "A")
This first hotkey makes the WindowsKey-NumpadEnter combination fling the active window to the next monitor. The window remains active, so you can fling it back (or fling it to your third(?!) monitor) right after.

#LButton::		Win__Fling(1, "M")
This second hotkey makes the WindowsKey-LeftMouseClick combination fling the clicked window to the next monitor.

Personally, I find the ability to specify an exact window ID not all that useful, but it was easy to provide so maybe you can think of an application.

I've been using this function for a few days now as I developed the code, and I have to say I am starting to use it pretty extensively. It's one of those things that you don't realize how useful it is until you get to use it.

Hope you like it as much as me!

bw800402
  • Guests
  • Last active:
  • Joined: --
I am just starting to look at ahk and like this script you have written. Is there a way to give it a list of window titles so that it will move multiple windows at a time based on a list? I am thinking it would be nice to launch all the apps i will be using at once and have them arranged for me. I have set window rules in my window manager about which virtual desktop I would like certain windows to go to, but it would be nice to also have them go to the desired monitor on each desktop. Right now they always launch on the first monitor. Thus with this script I am hoping to be able to specify which windows go to monitor 2 so that I can launch 7 or 8 windows at once and have them all go to their designated virtual desktops and their designated monitor.

Drinii
  • Guests
  • Last active:
  • Joined: --
**** yeah all i can say, this was awesome. i use dual monitors everyday, all day long. Thxxxxxxx

Desi
  • Members
  • 162 posts
  • Last active: Apr 15 2015 09:51 AM
  • Joined: 29 Oct 2010
Oh my goodness, that is pretty great. No more dragging windows like a sucker for me!

Thing is, my two monitors are of different resolutions, and the relative resizing shrinks windows that I've already pre-sized to fit in both. Perhaps add an extra parameter to the function to turn off resizing?

PatrickS
  • Members
  • 14 posts
  • Last active: Jul 20 2019 01:08 AM
  • Joined: 21 Mar 2010
Holy resurrected thread, Batman!

Thanks for the nice comments, everyone. Glad you like the code.

I'll take a look at the requests maybe this weekend and see if I can post an update. I think the no-resizing request from Desi is easy to do. The hard part would be to figure out what the "right" thing to do was when the flung window was bigger than it's new monitor... hmm....

PatrickS
  • Members
  • 14 posts
  • Last active: Jul 20 2019 01:08 AM
  • Joined: 21 Mar 2010
Here's an updated version of the function with a new function parameter (KeepRelativeSize) that lets the window maintain its original size. Set this parameter to false and the original window dimensions will be preserved after the fling.

Note that I've messed with the order of the parameters as well, so you may have to tweak your hotkey assignments. There's some suggestions as to what hotkeys to use inside the comment block at the top of the code.

Hope you like it.
Patrick

;; -----------------------------------------------------------------------
;; Fling (or shift) a window from one monitor to the next in a multi-monitor system.
;;
;; Function Parameters:
;;
;;      WinID            The window ID of the window to move. There are two special WinID
;;                     values supported:
;;
;;                     1) The value "A" means to use the Active window (default).
;;                     2) The value "M" means to use the window currently under the Mouse.
;;
;;		KeepRelativeSize	If true (default), the flung window is resized so as to keep
;;						the same *relative* size with respect to its new window. If false,
;;						the original window dimensions are preserved.
;;
;;      FlingDirection      The direction of the fling, expected to be either +1 (default) or -1.
;;                     The function is not limited to just two monitors; it supports
;;                     as many monitors as are currently connected to the system and
;;                     can fling a window serially through each of them in turn.
;;
;; The flinged window will be resized to have the same *relative* size in the new monitor.
;; For example, if the window originally occupied the entire right half of the screen,
;; it will again on the new monitor (assuming the window can be resized).
;;
;; Minimized windows are not modified; they are left exactly where they were.
;;
;; The return value of the function is non-zero if the window was successfully flung.
;;
;; Example hotkeys:
;;   #NumpadEnter::   Win__Fling("A")   ; Windows-NumpadEnter flings the active window
;;   #LButton::       Win__Fling("M")   ; Windows-LeftClick flings the window under the mouse
;;   #RButton::		  Win__Fling("M", false)	; Windows-RightClick flings the window under the
;;												;  mouse and keeps its original dimensions
;;
;; Copyright (c) 2010 Patrick Sheppard
;; All Rights Reserved

Win__Fling(WinID, KeepRelativeSize = true, FlingDirection = 1)
{
   ; Figure out which window to move based on the "WinID" function parameter:
   ;   1) The letter "A" means to use the Active window
   ;   2) The letter "M" means to use the window under the Mouse
   ; Otherwise, the parameter value is assumed to be the AHK window ID of the window to use.

   if (WinID = "A")
   {
      ; If the user supplied an "A" as the window ID, we use the Active window
      WinID := WinExist("A")
   }
   else if (WinID = "M")
   {
      ; If the user supplied an "M" as the window ID, we use the window currently under the Mouse
      MouseGetPos, MouseX, MouseY, WinID      ; MouseX & MouseY are retrieved but, for now, not used
   }

   ; Check to make sure we are working with a valid window
   IfWinNotExist, ahk_id %WinID%
   {
      ; Make a short noise so the user knows to stop expecting something fun to happen.
      SoundPlay, *64
      
      ; Debug Support
      ;MsgBox, 16, Window Fling: Error, Specified window does not exist.`nWindow ID = %WinID%

      return 0
   }

   ; Here's where we find out just how many monitors we're dealing with
   SysGet, MonitorCount, MonitorCount

   if (MonitorCount <= 1)
   {
      ; Honestly, there's not much to do in a one-monitor system
      return 1
   }

   ; For each active monitor, we get Top, Bottom, Left, Right of the monitor's
   ;  'Work Area' (i.e., excluding taskbar, etc.). From these values we compute Width and Height.
   ;  Results get put into variables named like "Monitor1Top" and "Monitor2Width", etc.,
   ;  with the monitor number embedded in the middle of the variable name.

   Loop, %MonitorCount%
   {
      SysGet, Monitor%A_Index%, MonitorWorkArea, %A_Index%
      Monitor%A_Index%Width  := Monitor%A_Index%Right  - Monitor%A_Index%Left
      Monitor%A_Index%Height := Monitor%A_Index%Bottom - Monitor%A_Index%Top
   }

   ; Retrieve the target window's original minimized / maximized state
   WinGet, WinOriginalMinMaxState, MinMax, ahk_id %WinID%

   ; We don't do anything with minimized windows (for now... this may change)
   if (WinOriginalMinMaxState = -1)
   {
      ; Debatable as to whether or not this should be flagged as an error
      return 0
   }
   
   ; If the window started out maximized, then the plan is to:
   ;   (a) restore it,
   ;   (b) fling it, then
   ;   (c) re-maximize it on the target monitor.
   ;
   ; The reason for this is so that the usual maximize / restore windows controls
   ; work as you'd expect. You want Windows to use the dimensions of the non-maximized
   ; window when you click the little restore icon on a previously flung (maximized) window.
   
   if (WinOriginalMinMaxState = 1)
   {
      ; Restore a maximized window to its previous state / size ... before "flinging".
      ;
      ; Programming Note: It would be nice to hide the window before doing this ...
      ; the window does some visual calisthenics that the user may construe as a bug.
      ; Unfortunately, if you hide a window then you can no longer work with it. <Sigh>

      WinRestore, ahk_id %WinID%
   }

   ; Retrieve the target window's original (non-maximized) dimensions
   WinGetPos, WinX, WinY, WinW, WinH, ahk_id %WinID%

   ; Find the point at the centre of the target window then use it
   ; to determine the monitor to which the target window belongs
   ; (windows don't have to be entirely contained inside any one monitor's area).
   
   WinCentreX := WinX + WinW / 2
   WinCentreY := WinY + WinH / 2

   CurrMonitor = 0
   Loop, %MonitorCount%
   {
      if (    (WinCentreX >= Monitor%A_Index%Left) and (WinCentreX < Monitor%A_Index%Right )
          and (WinCentreY >= Monitor%A_Index%Top ) and (WinCentreY < Monitor%A_Index%Bottom))
      {
         CurrMonitor = %A_Index%
         break
      }
   }

   ; Compute the number of the next monitor in the direction of the specified fling (+1 or -1)
   ;  Valid monitor numbers are 1..MonitorCount, and we effect a circular fling.
   NextMonitor := CurrMonitor + FlingDirection
   if (NextMonitor > MonitorCount)
   {
      NextMonitor = 1
   }
   else if (NextMonitor <= 0)
   {
      NextMonitor = %MonitorCount%
   }

   ; Scale the position / dimensions of the target window by the ratio of the monitor sizes.
   ; Programming Note: Do multiplies before divides in order to maintain accuracy in the integer calculation.
   WinFlingX := (WinX - Monitor%CurrMonitor%Left) * Monitor%NextMonitor%Width  // Monitor%CurrMonitor%Width  + Monitor%NextMonitor%Left
   WinFlingY := (WinY - Monitor%CurrMonitor%Top ) * Monitor%NextMonitor%Height // Monitor%CurrMonitor%Height + Monitor%NextMonitor%Top
   
   if KeepRelativeSize
   {
	   WinFlingW :=  WinW * Monitor%NextMonitor%Width  // Monitor%CurrMonitor%Width
      WinFlingH :=  WinH * Monitor%NextMonitor%Height // Monitor%CurrMonitor%Height
   }
   else
   {
      WinFlingW := WinW
	   WinFlingH := WinH
   }

   ; It's time for the target window to make its big move
   WinMove, ahk_id %WinID%,, WinFlingX, WinFlingY, WinFlingW, WinFlingH

   ; If the window used to be maximized, maximize it again on its new monitor
   if (WinOriginalMinMaxState = 1)
   {
      WinMaximize, ahk_id %WinID%
   }

   return 1
}


Pesho
  • Guests
  • Last active:
  • Joined: --
Just wanted to say thank you for this great script, it is of great help with a project i'm currently doing!

budRich
  • Members
  • 146 posts
  • Last active: Aug 03 2015 08:33 PM
  • Joined: 09 Aug 2011

Great script. I used ultramon only because it had this feature. AHK, cleaning up the systemtray since 2003



mhe
  • Members
  • 40 posts
  • Last active: Dec 04 2015 10:29 PM
  • Joined: 18 Oct 2007
Great script, i use it mainly to fling zoom player videos between a monitor and a TV. 
Not a biggie but if i send a fullscreen video two things happen, the video does not get centered right vertically, maybe because the monitor is 1920x1200 and the TV 1920x1080.
The other thing is, if i toggle fullscreen off, the window get's flung back to the other screen.
 
The solution is simple, i just send the video in a normal size and fullscreen it on the other device. But still, if you know what the issue is, do tell.


r4nd0m1
  • Members
  • 7 posts
  • Last active: Oct 26 2015 10:56 PM
  • Joined: 03 Apr 2014

Hi OP/all, I just realized how dearly I missed this function (, ever since some tool stopped working on win 8.1)!!!

 

Thank you very much for figuring this out and sharing it.

 

FWIW, here is the code I modified for my purposes:

#singleinstance force
runasadmin()
return

NumpadEnter::traytip,, % fling("M",1)

fling(id="A",dir=1)
{	if (id="A")
	  id:=winexist("A")
	else if (id="M")
	  mousegetpos,,, id		

	if not winexist("ahk_id " id)
	{ soundplay, *64
	  return 
	}

	sysget, #mon, monitorcount
    ifless, #mon, 2, return 
	
	loop % #mon
	{ sysget, mon%a_index%, monitorworkarea, %a_index%
	  mon%a_index%W:=mon%a_index%right-mon%a_index%left
	  mon%a_index%H:=mon%a_index%bottom-mon%a_index%top
	}

	winget, minmax, minmax
	ifequal, minmax, -1, return 
	ifequal, minmax, 1, winrestore
	
	wingetpos, x, y, w, h	
	xc:=x+w/2, yc:=y+h/2
	moncurr:=0
	loop % #mon
	{ if (xc>=mon%a_index%left) 
	  and (xc<mon%a_index%right) 
	  and (yc>=mon%a_index%top ) 
	  and (yc<mon%a_index%bottom)
	  {	moncurr:=a_index
		break
	  }
	}
	
	monnext:=((moncurr+dir)>#mon?1:(moncurr+dir)<1?#mon:(moncurr+dir))
	
	; Scale the position / dimensions of the target window by the ratio of the monitor sizes.
	; Programming Note: Do multiplies before divides in order to maintain accuracy in the integer calculation.
	xn:=(x-mon%moncurr%left)*mon%monnext%W // mon%moncurr%W+mon%monnext%left
	yn:=(y-mon%moncurr%top)*mon%monnext%H // mon%moncurr%H+mon%monnext%top
	wn:=w*mon%monnext%W  // mon%moncurr%W
	hn:=h*mon%monnext%H // mon%moncurr%H	
	winmove, ahk_id %id%,, xn, yn, wn, hn
	ifequal, minmax, 1, winmaximize
	return "id: " id " dir: " dir
	 . "`nx/y:`t" x "/" y "`nw/h:`t" w "/" h 
	 . "`nxc/yc:`t" round(xc) "/" round(yc)
	 . "`nxn/yn:`t" xn "/" yn "`nwn/hn:`t" wn "/" hn 
}

runasadmin()
{ ifequal, a_isadmin, 1, return
  run *runas "%a_scriptfullpath%" 
  exitapp
}


Skrell
  • Members
  • 384 posts
  • Last active: Jul 07 2016 05:03 PM
  • Joined: 23 Aug 2011

 

Hi OP/all, I just realized how dearly I missed this function (, ever since some tool stopped working on win 8.1)!!!

 

Thank you very much for figuring this out and sharing it.

 

FWIW, here is the code I modified for my purposes:

#singleinstance force
runasadmin()
return

NumpadEnter::traytip,, % fling("M",1)

fling(id="A",dir=1)
{	if (id="A")
	  id:=winexist("A")
	else if (id="M")
	  mousegetpos,,, id		

	if not winexist("ahk_id " id)
	{ soundplay, *64
	  return 
	}

	sysget, #mon, monitorcount
    ifless, #mon, 2, return 
	
	loop % #mon
	{ sysget, mon%a_index%, monitorworkarea, %a_index%
	  mon%a_index%W:=mon%a_index%right-mon%a_index%left
	  mon%a_index%H:=mon%a_index%bottom-mon%a_index%top
	}

	winget, minmax, minmax
	ifequal, minmax, -1, return 
	ifequal, minmax, 1, winrestore
	
	wingetpos, x, y, w, h	
	xc:=x+w/2, yc:=y+h/2
	moncurr:=0
	loop % #mon
	{ if (xc>=mon%a_index%left) 
	  and (xc<mon%a_index%right) 
	  and (yc>=mon%a_index%top ) 
	  and (yc<mon%a_index%bottom)
	  {	moncurr:=a_index
		break
	  }
	}
	
	monnext:=((moncurr+dir)>#mon?1:(moncurr+dir)<1?#mon:(moncurr+dir))
	
	; Scale the position / dimensions of the target window by the ratio of the monitor sizes.
	; Programming Note: Do multiplies before divides in order to maintain accuracy in the integer calculation.
	xn:=(x-mon%moncurr%left)*mon%monnext%W // mon%moncurr%W+mon%monnext%left
	yn:=(y-mon%moncurr%top)*mon%monnext%H // mon%moncurr%H+mon%monnext%top
	wn:=w*mon%monnext%W  // mon%moncurr%W
	hn:=h*mon%monnext%H // mon%moncurr%H	
	winmove, ahk_id %id%,, xn, yn, wn, hn
	ifequal, minmax, 1, winmaximize
	return "id: " id " dir: " dir
	 . "`nx/y:`t" x "/" y "`nw/h:`t" w "/" h 
	 . "`nxc/yc:`t" round(xc) "/" round(yc)
	 . "`nxn/yn:`t" xn "/" yn "`nwn/hn:`t" wn "/" hn 
}

runasadmin()
{ ifequal, a_isadmin, 1, return
  run *runas "%a_scriptfullpath%" 
  exitapp
}

What does this code change behavior wise? 



KyletheNinja
  • Members
  • 1 posts
  • Last active: Dec 04 2014 10:49 PM
  • Joined: 04 Dec 2014

Works great on three monitors! Just loops the windows around if you keep clickin em