Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

Secondary Monitor


  • Please log in to reply
70 replies to this topic
Goodfare
  • Guests
  • Last active:
  • Joined: --
Is it possible to enable/disable a secondary monitor using a hotkey?
If so can someone please explain.

Superfraggle
  • Members
  • 1019 posts
  • Last active: Sep 25 2011 01:06 AM
  • Joined: 02 Nov 2004
This can be done by one of two methods I believe

1st would be by automating the usual process of enabling the second monitor, script recorder could make a basic one.

2nd would be several dll calls I believe

EnumDisplayDevices
and
ChangeDisplaySettingsEx

I am still learning the dllcalls side so the second is beyond me :(
Steve F AKA Superfraggle

http://r.yuwie.com/superfraggle

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I figured out how to enable a monitor by using ChangeDisplaySettingsEx, but not how to disable it. Fortunately there is a solution using RegWrite. The following script should disable or enable the secondary monitor when run. (To be more accurate, it disables or enables the "Extend the desktop onto this monitor" setting.)
;
; AutoHotkey Version: 1.0.47.02
; Language:       English
; Platform:       Win9x/NT ??
; Author:         Lexikos
;
; Script Function:
;	Enable or disable a secondary monitor. ("Extend the desktop onto this monitor")
;

#NoEnv
#NoTrayIcon

GetDisplayDeviceList("Display", false)

if Display2
{
    RegRead, Attached, HKEY_CURRENT_CONFIG
        , System\CurrentControlSet\Control\VIDEO\%Display2ID%\0001
        , Attach.ToDesktop
    
    ; This part is in case I get the Display..ID wrong,
    ; so I don't write junk into the registry.
    if (ErrorLevel) {
        MsgBox, Error getting display attached status.
        return
    }
    
    Attached := ! Attached
    
    RegWrite, REG_DWORD, HKEY_CURRENT_CONFIG
        , System\CurrentControlSet\Control\VIDEO\%Display2ID%\0001
        , Attach.ToDesktop
        , %Attached%
    
    ; Apply display settings from registry.
    DllCall("ChangeDisplaySettings", "UInt", 0, "UInt", 0)
}

ExitApp


; Creates a global array using ArrayName as the base.
;   %ArrayName%             Number of display devices.
;   %ArrayName%Primary      The index of the primary display device.
;   %ArrayName%%N%          Name of display device (used by ChangeDisplaySettingsEx.)
;   %ArrayName%%N%ID        {GUID} value required for some registry operations.
; Only set if DesktopOnly=false:
;   %ArrayName%%N%Attached  =1 if "Extend the desktop onto this monitor" is enabled.
GetDisplayDeviceList(ArrayName, DesktopOnly=true)
{
    local DisplayDevice, Count, DeviceName, StateFlags, DeviceKey
    
    ; DISPLAY_DEVICE DisplayDevice
    VarSetCapacity(DisplayDevice, 424)
    ; lpDisplayDevice.cb := sizeof(DISPLAY_DEVICE)
    NumPut(424, DisplayDevice, 0)
    
    if %ArrayName%Primary
        %ArrayName%Primary =
    
    VarSetCapacity(DeviceName,  32, 0)
    VarSetCapacity(DeviceKey , 128, 0)
    
    Loop
    {
        if ! DllCall("EnumDisplayDevices"
            , "UInt", 0         ; lpDevice (NULL: use iDevNum to identify devices)
            , "UInt", A_Index-1 ; iDevNum
            , "UInt", &DisplayDevice ; lpDisplayDevice
            , "UInt", 0)        ; dwFlags
            break
        
        StateFlags := NumGet(DisplayDevice, 164)
        
        ; Useful DISPLAY_DEVICE.StateFlags:
        ;   #define DISPLAY_DEVICE_ATTACHED_TO_DESKTOP 0x00000001
        ;   #define DISPLAY_DEVICE_PRIMARY_DEVICE      0x00000004
        ;   #define DISPLAY_DEVICE_MIRRORING_DRIVER    0x00000008

        if (StateFlags & 8) ; always exclude pseudo-devices
            continue
        if (DesktopOnly && !(StateFlags & 1))
            continue
        
        Count += 1

        DllCall("lstrcpynA", "Str", DeviceName  , "UInt", &DisplayDevice+4  , "int", 32)
        DllCall("lstrcpynA", "Str", DeviceKey   , "UInt", &DisplayDevice+296, "int", 128)
        
        %ArrayName%%Count% := DeviceName
        
        ; DeviceKey is something like:
        ;   \Registry\Machine\System\CurrentControlSet\Control\Video\{device-specific-value}\0000
        ; We want the:
        ;   {device-specific-value}
        SplitPath, DeviceKey,, DeviceKey ; "up one level"
        SplitPath, DeviceKey, %ArrayName%%Count%ID
        
        if (StateFlags & 4)
            %ArrayName%Primary := Count
        
        if (!DesktopOnly)
            %ArrayName%%Count%Attached := (StateFlags&1)
        ; else: function only returns attached monitors, no need for %..%Attached.
    }
    
    %ArrayName% := Count
}
If the GetDisplayDeviceList() function seems overly complex, that's probably because it's not specifically written for this script. For the RegWrite solution, it's only required because different display devices need a different path in the registry.

Note the "\0001" part of the registry key is the four digit zero-based index of the monitor.

Also, this won't necessarily work the same on all multi-monitor setups. It should at least work on any setups where one graphics card is powering multiple monitors.

novis
  • Guests
  • Last active:
  • Joined: --
i think this is what im looking for. could you show how to bind a hotkey to this function? thanks

also is it possible to switch off primary and switch to secondary instead of extend?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
The registry-editing approach doesn't work for the primary monitor. Since my last post, I've figured out how to use ChangeDisplaySettingsEx to enable or disable a display device. Unfortunately it doesn't preserve the position(s) of the display(s).

For instance, if I disable and re-enable my secondary display, it will move from the right side of my primary to the left side. (If it's already on the left side, it should stay where it is.) Disabling and re-enabling my primary display seems to work without problems, though.

The hotkeys I use are:
#+NumpadMult::EnableDisplayDevice("\\.\DISPLAY1", -1)  ; toggle primary
#+NumpadDiv::EnableDisplayDevice("\\.\DISPLAY2", -1)  ; toggle secondary
Since the device names might be different, this might not work for you. If that is the case, let me know and I'll post a (better) function for getting the right device name (or you could use GetDisplayDeviceList() from my previous post.)

EnableDisplayDevice.ahk:
; Enables, disables or toggles a display device.
;
; DeviceName:   The name of the device, e.g. \\.\DISPLAY1
; Action:       The action to take.
;                    0   Disable
;                    1   Enable
;                   -1   Toggle (may not be reliable if NoReset=true)
; NoReset:      If true, settings will be saved to the registry, but not applied.
;
; The following can be used to apply settings saved in the registry:
;   DllCall("ChangeDisplaySettings", "uint", 0, "uint", 1)
;
; Return values:
;    DISP_CHANGE_SUCCESSFUL       0
;    DISP_CHANGE_RESTART          1
;    DISP_CHANGE_FAILED          -1
;    DISP_CHANGE_BADMODE         -2
;    DISP_CHANGE_NOTUPDATED      -3
;    DISP_CHANGE_BADFLAGS        -4
;    DISP_CHANGE_BADPARAM        -5
;
; Examples:
;   ; disable display 2
;     EnableDisplayDevice("\\.\DISPLAY2", 0)
;     Sleep, 10000
;
;   ; simultaneously enable display 2 and disable display 1
;     EnableDisplayDevice("\\.\DISPLAY2", 1, true)
;     EnableDisplayDevice("\\.\DISPLAY1", 0)
;     Sleep, 10000
;
;   ; ensure both are enabled
;     EnableDisplayDevice("\\.\DISPLAY2", 1, true)
;     EnableDisplayDevice("\\.\DISPLAY1")
;
; Note: DeviceNames may vary. Rather than hard-coding the device name,
;       EnumDisplayDevices() should be used to enumerate the devices.
;
EnableDisplayDevice(DeviceName, Action=1, NoReset=false)
{
    if (Action = -1)
    {   ; Determine if the display should be enabled or disabled.
        Loop {
            if ! EnumDisplayDevices(A_Index, this_name, this_state)
                break
            if (this_name = DeviceName) {
                Action := !(this_state & 1) ; DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
                break
            }
        }
        ; If Action is still -1, an invalid DeviceName was specified.
        ; The script will attempt to enable the display device, but
        ; ChangeDisplaySettingsEx() will most likely return error -5.
    }

    VarSetCapacity(devmode, 156, 0)
    NumPut(156, devmode, 36, "UShort")

    ; Set DEVMODE.dmFields to indicate which fields are valid.
    if (Action) ; Enable
        NumPut(0x000020, devmode, 40)   ; position={0,0}
    else        ; Disable
        NumPut(0x180020, devmode, 40)   ; width=0, height=0, position={0,0}

    ; Since CDS_NORESET is specified here, if NoReset=true, the user must
    ; manually call ChangeDisplaySettings(NULL,1) or restart the computer.
    err := DllCall("ChangeDisplaySettingsEx", "str", DeviceName
        , "uint", &devmode, "uint", 0, "uint", 0x10000001, "uint", 0)
    
    ; ChangeDisplaySettings() is called here for two reasons:
    ;   - A restart is otherwise required to enable a secondary display device.
    ;       See: http://support.microsoft.com/kb/308216
    ;   - Disabling display devices with just ChangeDisplaySettingsEx()
    ;     tends to leave them turned on.
    if (!err && !NoReset)
        err := DllCall("ChangeDisplaySettings", "uint", 0, "uint", 1)
    
    return err
}
I have it saved as EnableDisplayDevice.ahk in my user library, so that no #include is necessary.


To bind my previous script to a hotkey (to toggle the secondary display), simply put the hotkey (e.g. #+NumpadMult::) before
GetDisplayDeviceList("Display", false)
and return instead of ExitApp.

novis
  • Guests
  • Last active:
  • Joined: --

#+NumpadMult::EnableDisplayDevice("\\.\DISPLAY1", -1) ; toggle primary
#+NumpadDiv::EnableDisplayDevice("\\.\DISPLAY2", -1) ; toggle secondary


thank you

Unfortunately it doesn't preserve the position(s) of the display(s).

np in my case. i got a plasma on dvi#2 for movies so i dont want the primary display to be on at all after the switch.

My matrox driver only let me Extend even though they call it "independent mode". With my old quadro card and nvidia driver i could simple choose to switch to the other display ie change output from DVI1 to DVI2... such a simple task must be possible?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

My matrox driver only let me Extend even though they call it "independent mode". With my old quadro card and nvidia driver i could simple choose to switch to the other display ie change output from DVI1 to DVI2... such a simple task must be possible?

You mean have only one display on at a time? There's an expample in the comments of my script how to simultaneously enable one display and disable the other (i.e. swap.)

novis
  • Guests
  • Last active:
  • Joined: --
yes thats what i need! its a nice script ..i just need it to work.

i get a error enumerating the device. how do i hardcode it for my display? or do u know what might be the problem?

--> if ! EnumDisplayDevices(A_Index, this_name, this_state)



engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
what is the error? function not found or some syntax issue? what version of ahk are you using?

tic
  • Members
  • 1934 posts
  • Last active: May 30 2018 08:13 PM
  • Joined: 22 Apr 2007
i recommend just using wizmo:

<!-- m -->http://www.grc.com/wizmo/wizmo.htm<!-- m -->

and putting it in the same folder as your ahk, and then just running it with the argument to turn off your 2nd monitor.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

i get a error enumerating the device. how do i hardcode it for my display? or do u know what might be the problem?

You need EnumDisplayDevices, which I hadn't posted.
; EnumDisplayDevices(Index [, ByRef Name, ByRef StateFlags ] )
;
; Index:        One-based index of device to get info for.
; DeviceName:   [out] The name of the device.
; StateFlags:   [out] Any reasonable combination of the following flags:
;   0x00000001      DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
;   0x00000004      DISPLAY_DEVICE_PRIMARY_DEVICE
;   0x00000008      DISPLAY_DEVICE_MIRRORING_DRIVER
; DeviceKey:    [out] Path to the device's registry key relative to HKEY_LOCAL_MACHINE.
;
; Returns true if the display device exists, otherwise false.
;
/* Example 1 (requires EnableDisplayDevice()):
    SecondaryDevice =
    count = 0
    Loop {
        if ! EnumDisplayDevices(A_Index, DeviceName, StateFlags)
            break
        if !(StateFlags & 8) ; not a pseudo-device
            if (++count = 2) ; second device
                break
    }
    if DeviceName
        EnableDisplayDevice(DeviceName, -1) ; toggle
*/
/* Example 2:
    Loop {
        if ! EnumDisplayDevices(A_Index, DeviceName, StateFlags)
            break
        if (StateFlags & 4)
            text .= DeviceName " is the primary display device.`n"
        else if (StateFlags & 1)
            text .= "The desktop extends onto " DeviceName ".`n"
        if (StateFlags & 8)
            text .= DeviceName " is a pseudo-device.`n"
    }
    MsgBox %text%
*/
EnumDisplayDevices(Index, ByRef DeviceName, ByRef StateFlags="", ByRef DeviceKey="")
{
    ; DISPLAY_DEVICE DisplayDevice
    VarSetCapacity(DisplayDevice, 424)
    ; lpDisplayDevice.cb := sizeof(DISPLAY_DEVICE)
    NumPut(424, DisplayDevice, 0)
    
    VarSetCapacity(DeviceName, 32, 0)
    VarSetCapacity(DeviceKey, 128, 0)
    ; For consistency, clear StateFlags in case of failure.
    StateFlags = 0
    
    if ! DllCall("EnumDisplayDevices"
        , "UInt", 0
        , "UInt", Index-1
        , "UInt", &DisplayDevice
        , "UInt", 0)
        return false
    
    StateFlags := NumGet(DisplayDevice, 164)
    DllCall("lstrcpynA", "Str", DeviceName, "UInt", &DisplayDevice+4,   "int", 32)
    DllCall("lstrcpynA", "Str", DeviceKey,  "UInt", &DisplayDevice+296, "int", 128)
    if (SubStr(DeviceKey,1,18)="\Registry\Machine")
        DeviceKey := SubStr(DeviceKey,19)
    return true
}
Example 2 (in the comments) can be used to list all of the display devices.

novis
  • Guests
  • Last active:
  • Joined: --
just got back from work so i havent tested anything yet but thanks everyone helping me out. Nice community!

novis
  • Guests
  • Last active:
  • Joined: --
this might be too much for my fragile little mind ...how should i use example2 in the EnumDisplayDevices-code, replace it in the 2nd script?

Is this a pseudo/place-holding-name that i should replace: \\.\DISPLAY1 ?
should i un-comment anything before i can run it?

-----
Version 1.0.47.04 of AHK
Winxp sp2
Matrox p650 pci-e

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
No, \\.\DISPLAY1 is an actual display device name. EnumDisplayDevices Example 2 outputs this on my system:

\\.\DISPLAY1 is the primary display device.
The desktop extends onto \\.\DISPLAY2.
\\.\DISPLAYV1 is a pseudo-device.
\\.\DISPLAYV2 is a pseudo-device.

\\.\DISPLAY1 is my primary display, while \\.\DISPLAY2 is my secondary. \\.\DISPLAYV1 and \\.\DISPLAYV2 are "mirroring drivers," i.e. they don't exist. It may be possible for the desktop to extend onto a pseudo-device, in which case the script would output something like:

The desktop extends onto \\.\DISPLAYV1.
\\.\DISPLAYV1 is a pseudo-device.

The exact code I use for my hotkeys is just what I wrote in my previous post:
#+NumpadMult::EnableDisplayDevice("\\.\DISPLAY1", -1)
#+NumpadDiv::EnableDisplayDevice("\\.\DISPLAY2", -1)
\\.\DISPLAY1 is a display device name, and -1 means "toggle." 0 is disable, and 1 is enable.

novis
  • Guests
  • Last active:
  • Joined: --
damn. then it doesnt work for me :/

just to be sure. i got this in a new .ahk and i press: LWin+*, LWin+\ (on Numpad)


#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.


#+NumpadMult::EnableDisplayDevice("\\.\DISPLAY1", -1)
#+NumpadDiv::EnableDisplayDevice("\\.\DISPLAY2", -1)


; EnumDisplayDevices(Index [, ByRef Name, ByRef StateFlags ] )
;
; Index:        One-based index of device to get info for.
; DeviceName:   [out] The name of the device.
; StateFlags:   [out] Any reasonable combination of the following flags:
;   0x00000001      DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
;   0x00000004      DISPLAY_DEVICE_PRIMARY_DEVICE
;   0x00000008      DISPLAY_DEVICE_MIRRORING_DRIVER
; DeviceKey:    [out] Path to the device's registry key relative to HKEY_LOCAL_MACHINE.
;
; Returns true if the display device exists, otherwise false.
;
/* Example 1 (requires EnableDisplayDevice()):
    SecondaryDevice =
    count = 0
    Loop {
        if ! EnumDisplayDevices(A_Index, DeviceName, StateFlags)
            break
        if !(StateFlags & 8) ; not a pseudo-device
            if (++count = 2) ; second device
                break
    }
    if DeviceName
        EnableDisplayDevice(DeviceName, -1) ; toggle
*/
/* Example 2:
    Loop {
        if ! EnumDisplayDevices(A_Index, DeviceName, StateFlags)
            break
        if (StateFlags & 4)
            text .= DeviceName " is the primary display device.`n"
        else if (StateFlags & 1)
            text .= "The desktop extends onto " DeviceName ".`n"
        if (StateFlags & 8)
            text .= DeviceName " is a pseudo-device.`n"
    }
    MsgBox %text%
*/
EnumDisplayDevices(Index, ByRef DeviceName, ByRef StateFlags="", ByRef DeviceKey="")
{
    ; DISPLAY_DEVICE DisplayDevice
    VarSetCapacity(DisplayDevice, 424)
    ; lpDisplayDevice.cb := sizeof(DISPLAY_DEVICE)
    NumPut(424, DisplayDevice, 0)
   
    VarSetCapacity(DeviceName, 32, 0)
    VarSetCapacity(DeviceKey, 128, 0)
    ; For consistency, clear StateFlags in case of failure.
    StateFlags = 0
   
    if ! DllCall("EnumDisplayDevices"
        , "UInt", 0
        , "UInt", Index-1
        , "UInt", &DisplayDevice
        , "UInt", 0)
        return false
   
    StateFlags := NumGet(DisplayDevice, 164)
    DllCall("lstrcpynA", "Str", DeviceName, "UInt", &DisplayDevice+4,   "int", 32)
    DllCall("lstrcpynA", "Str", DeviceKey,  "UInt", &DisplayDevice+296, "int", 128)
    if (SubStr(DeviceKey,1,18)="\Registry\Machine\")
        DeviceKey := SubStr(DeviceKey,19)
    return true
}

; Enables, disables or toggles a display device.
;
; DeviceName:   The name of the device, e.g. \\.\DISPLAY1
; Action:       The action to take.
;                    0   Disable
;                    1   Enable
;                   -1   Toggle (may not be reliable if NoReset=true)
; NoReset:      If true, settings will be saved to the registry, but not applied.
;
; The following can be used to apply settings saved in the registry:
;   DllCall("ChangeDisplaySettings", "uint", 0, "uint", 1)
;
; Return values:
;    DISP_CHANGE_SUCCESSFUL       0
;    DISP_CHANGE_RESTART          1
;    DISP_CHANGE_FAILED          -1
;    DISP_CHANGE_BADMODE         -2
;    DISP_CHANGE_NOTUPDATED      -3
;    DISP_CHANGE_BADFLAGS        -4
;    DISP_CHANGE_BADPARAM        -5
;
; Examples:
;   ; disable display 2
;     EnableDisplayDevice("\\.\DISPLAY2", 0)
;     Sleep, 10000
;
;   ; simultaneously enable display 2 and disable display 1
;     EnableDisplayDevice("\\.\DISPLAY2", 1, true)
;     EnableDisplayDevice("\\.\DISPLAY1", 0)
;     Sleep, 10000
;
;   ; ensure both are enabled
;     EnableDisplayDevice("\\.\DISPLAY2", 1, true)
;     EnableDisplayDevice("\\.\DISPLAY1")
;
; Note: DeviceNames may vary. Rather than hard-coding the device name,
;       EnumDisplayDevices() should be used to enumerate the devices.
;
EnableDisplayDevice(DeviceName, Action=1, NoReset=false)
{
    if (Action = -1)
    {   ; Determine if the display should be enabled or disabled.
        Loop {
            if ! EnumDisplayDevices(A_Index, this_name, this_state)
                break
            if (this_name = DeviceName) {
                Action := !(this_state & 1) ; DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
                break
            }
        }
        ; If Action is still -1, an invalid DeviceName was specified.
        ; The script will attempt to enable the display device, but
        ; ChangeDisplaySettingsEx() will most likely return error -5.
    }

    VarSetCapacity(devmode, 156, 0)
    NumPut(156, devmode, 36, "UShort")

    ; Set DEVMODE.dmFields to indicate which fields are valid.
    if (Action) ; Enable
        NumPut(0x000020, devmode, 40)   ; position={0,0}
    else        ; Disable
        NumPut(0x180020, devmode, 40)   ; width=0, height=0, position={0,0}

    ; Since CDS_NORESET is specified here, if NoReset=true, the user must
    ; manually call ChangeDisplaySettings(NULL,1) or restart the computer.
    err := DllCall("ChangeDisplaySettingsEx", "str", DeviceName
        , "uint", &devmode, "uint", 0, "uint", 0x10000001, "uint", 0)
   
    ; ChangeDisplaySettings() is called here for two reasons:
    ;   - A restart is otherwise required to enable a secondary display device.
    ;       See: http://support.microsoft.com/kb/308216
    ;   - Disabling display devices with just ChangeDisplaySettingsEx()
    ;     tends to leave them turned on.
    if (!err && !NoReset)
        err := DllCall("ChangeDisplaySettings", "uint", 0, "uint", 1)
   
    return err
}