Bwidget Source Code
View Ticket
Not logged in
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.
Ticket UUID: b64e03e54890420e8c80c6ad94d04f49fdceee2d
Title: Multi monitors issue on Windows
Type: Patch Version: 1.9.5
Submitter: anonymous Created on: 2015-09-12 15:13:56
Subsystem: bwidget 1.x Assigned To: oehhar
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2015-11-04 09:57:46
Resolution: Fixed Closed By: oehhar
    Closed on: 2015-11-04 09:57:46
Description:
Hello ,
I noticed that in the file "dynhelp.tcl" BWidget 1.9.5, There is a comment stating
:
        # On windows multi screen configurations, coordinates may get outside
        # the main screen. We suppose that all screens have the same size
        # because it is not possible to query the size of the other screens.

Actually i faced this issue in my work and i came up with the following
implementation to handle
multi screens in Windows using the windows registry
#This Function return Info about all the Monitors connected in Case of Windows Platform
#Function Input  : None
#Function Output : list of lists, each sub list contains info about one of the screens.

#Example :
#Assume you have two screens, the output will look like {{0x0 0x0 0x690 0x41a} {0xfffffb00 0x0 0x500 0x400}}
#The sub list contains in order relativeX , realtiveY , resolutionX , resolutionY for each screen
#For primary screen relativeX and relativeY should be Zeros, and the relativeX and relativeY of the other screen is related to it.
#In the above case the primary screen is on the right, thats why the relativeX of the second screen is negative.
#The resolutionX and resolutionY is the resolution of each screen.
# Note that i grep these values from the registry thats why they appear in Hex.

#By comparing the relativeX and relativeY values of all the screens, you should be
#able to easily allocate your primary screen and the location of other screens with respect to it.

#Function was tested under Tcl8.4 under cygwin (Windows XP).
#Function was tested under Tcl8.4 under Linux  (RH4).
#Function was tested under Tcl8.5 Active state (Windows 7).
#Function was tested under Tcl8.5 under cygwin (Windows 7).


proc getMonitorsInfo {} {
 global tcl_platform
 set resultList ""
 if {$tcl_platform(os) != "Linux"} {
# get the number of monitors
   set monitors [exec reg query {HKLM\SYSTEM\CurrentControlSet\services\monitor\Enum }]
   set monitors [exec echo $monitors | grep Count]
   set monitors [split $monitors " " ]
   set monitors [lindex $monitors end]
# search the registry to get the screens resolutions and find the main screen
   set my_query "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO\\"
   set tmp [exec reg query $my_query]
# split the result into lines to loop on them
   set tmp [split $tmp "\n"] 
#loop to find all under \\VIDEO
   for {set it 0} {$it < [llength $tmp] } {incr it} {
        set my_query "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO\\"
# get the line
        set for_tmp [lindex $tmp $it]
        #puts $for_tmp
# split the line by backslashes
        set for_tmp [split $for_tmp "\\"] 
# get the last item after splitting,will be like "{21B5061B-2F71-4D25-8BB5-5CD5A8833140}"        
        set for_tmp [lindex $for_tmp end] 
#make sure empty strings in the list are not used
        if { [string length $for_tmp] < 10 } {
         continue
        }
        #puts $for_tmp
# concat the original query with the new part ,and execute it
         set my_query $my_query$for_tmp 
         set temp [exec reg query $my_query]
# split again to loop on entries of the sub key line by line
        set temp [split $temp "\n"] 
        #puts $temp
#loop to find all under each sub key of \\VIDEO
        for {set i 0} {$i < [llength $temp]} {incr i} {
# get each line
            set for_temp [lindex $temp $i ] 
          #puts $for_temp
# make sure empty strings in the list are not used
     if { [string length $for_temp] < 10 } {
             continue
     }
# get the values of resolution x , resolution y , relative x , and attach.todektop
# execute query with the new sub key
     set for_temp [exec reg query $for_temp] 
# grep on the values you want
     set att_x [exec echo $for_temp | grep Attach.ToDesktop] 
     set att_x [split $att_x " "]
     set att_x [lindex $att_x end]
#if attach.todesktop != 1, then this screen is not used, ignore it
#else check if this is the main screen (relative x == 0)
     if {$att_x != "0x01"} {
      break
     }
     set monitorList ""
     set rel_x [exec echo $for_temp | grep Attach.RelativeX]
     set rel_x [split $rel_x " "]
     set rel_x [lindex $rel_x end]
     lappend monitorList $rel_x
     set rel_y [exec echo $for_temp | grep Attach.RelativeY]
     set rel_y [split $rel_y " "]
     set rel_y [lindex $rel_y end]
     lappend monitorList $rel_y
     set res_x [exec echo $for_temp | grep XResolution]
     set res_x [split $res_x " "]
     set res_x [lindex $res_x end]
     lappend monitorList $res_x
     set res_y [exec echo $for_temp | grep YResolution]
     set res_y [split $res_y " "]
     set res_y [lindex $res_y end]
     lappend monitorList $res_y
     lappend resultList $monitorList
      }
   }
    return $resultList
 } else {
  puts "This Function is not supported in Linux"
  return $resultList
 }
}
#just for testing
set x [getMonitorsInfo]
puts $x
User Comments: oehhar added on 2015-11-04 09:57:46:

The proposed code has some advantages.

Nevertheless, this may be done to some limits with vrootx/vrooty with a little amount of code. This was implemented by checkin [0371bea522].

This does not cover the two cases: - dynhelp may be on a screen border of two virtual screens - On windows, a screen portion on the virtual screen may have no real representation, if the screens have different heights.

Point 1 is accepted to be compatible to Unix.

Point 2 is accepted as minor drawback

Thank you, I am closing the ticket. Maybee, the contributed code may be copied somewhere else...


oehhar added on 2015-10-23 14:06:03:

The following twapi command gives the same result in some other words:

twapi::get_multiple_display_monitor_info
{-extent {0 0 1920 1080} -workarea {0 0 1920 1040} -primary 1 -name {\\.\DISPLAY1}}\
{-extent {1920 0 3200 720} -workarea {1920 0 3200 680} -primary 0 -name {\\.\DISPLAY2}}

Basically, the contents of the rectangles in -extend give valid monitor coordinates.


oehhar added on 2015-10-23 13:28:49:

apn commented on the wiki:

twapi does have get_display_monitors and get_display_monitor_info commands for that purpose


oehhar added on 2015-10-23 12:27:04:

I have tested the code.

For me, it results in to many monitors. This is due to the fact, that I have multiple control sets under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\UnitedVideo\CONTROL\VIDEO

The key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\IDConfigDB->CurrentConfig

tells you, which is the current.

So, only this key should be taken.

Here is the modified code:

#This Function return Info about all the Monitors connected in Case of Windows Platform
#Function Input  : None
#Function Output : list of lists, each sub list contains info about one of the screens.

#Example :
#Assume you have two screens, the output will look like {{0 0 1680 1050} {-1280 0 1280 1024}}
#The sub list contains in order relativeX , realtiveY , resolutionX , resolutionY for each screen
#For primary screen relativeX and relativeY should be Zeros, and the relativeX and relativeY of the other screen is related to it.
#In the above case the primary screen is on the right, thats why the relativeX of the second screen is negative.
#The resolutionX and resolutionY is the resolution of each screen.

#By comparing the relativeX and relativeY values of all the screens, you should be
#able to easily allocate your primary screen and the location of other screens with respect to it.

proc getMonitorsInfo {} {
    # > This will fail on non-windows
    package require registry
    set resultList {}
    set rootKey {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\UnitedVideo\CONTROL\VIDEO}
    # get the list of monitor control set subkeys
    set keyList [registry keys $rootKey]
    # If there is only one->take it, otherwise look for current
    switch -exact -- [llength $keyList] {
	0 {	return -code error "No monitor information" }
	1 {	set keyCur [lindex $keyList 0] }
	default {
	    # Search the active set (0 based index) if there are multiple
	    set currentSet [registry get\
		    {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\IDConfigDB}\
		    CurrentConfig]
	    if {$currentSet < 0 || $currentSet >= [llength $keyList]} {
		return -code error "Wrong current set"
	    }
	    set keyCur [lindex $keyList $currentSet]
	}
    }
    append rootKey "\\" $keyCur
    # loop over the monitor subkeys
    foreach keyCur [registry keys $rootKey] {
	set monitorKey ${rootKey}\\$keyCur
	#if attach.todesktop != 1, then this screen is not used, ignore it
	if { ![string equal "1" [registry get $monitorKey Attach.ToDesktop]] } {
	    continue
	}
	lappend resultList [list\
		[registry get $monitorKey Attach.RelativeX]\
		[registry get $monitorKey Attach.RelativeY]\
		[registry get $monitorKey DefaultSettings.XResolution]\
		[registry get $monitorKey DefaultSettings.YResolution]]
    }
    return $resultList
}


anonymous (claiming to be Remove the exec at all, and now the function result is in Decimal not Hex) added on 2015-09-14 09:51:59:
#This Function return Info about all the Monitors connected in Case of Windows Platform
#Function Input  : None
#Function Output : list of lists, each sub list contains info about one of the screens.

#Example :
#Assume you have two screens, the output will look like {{0 0 1680 1050} {-1280 0 1280 1024}}
#The sub list contains in order relativeX , realtiveY , resolutionX , resolutionY for each screen
#For primary screen relativeX and relativeY should be Zeros, and the relativeX and relativeY of the other screen is related to it.
#In the above case the primary screen is on the right, thats why the relativeX of the second screen is negative.
#The resolutionX and resolutionY is the resolution of each screen.

#By comparing the relativeX and relativeY values of all the screens, you should be
#able to easily allocate your primary screen and the location of other screens with respect to it.

#Function was tested under Tcl8.4 under cygwin (Windows XP).
#Function was tested under Tcl8.4 under Linux  (RH4).
#Function was tested under Tcl8.5 Active state (Windows 7).
#Function was tested under Tcl8.5 under cygwin (Windows 7).

package require registry

proc getMonitorsInfo {} {
 global tcl_platform
 set resultList ""
 if {$tcl_platform(os) != "Linux"} {
# get the number of monitors
# search the registry to get the screens resolutions and find the main screen
   set tmp [registry keys HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO]
   #puts $tmp
#loop to find all under \\VIDEO
   for {set it 0} {$it < [llength $tmp] } {incr it} {
        set my_query "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO\\"
# get the line
        set for_tmp [lindex $tmp $it]
       # puts $for_tmp
#make sure empty strings in the list are not used
        if { [string length $for_tmp] < 10 } {
         continue
        }
        #puts $for_tmp
# concat the original query with the new part ,and execute it
         set my_query $my_query$for_tmp 
         set temp [registry keys $my_query]
        # puts $temp
#loop to find all under each sub key of \\VIDEO
        for {set i 0} {$i < [llength $temp]} {incr i} {
# get each line
            set for_temp [lindex $temp $i ] 
            #puts $for_temp
# get the values of resolution x , resolution y , relative x , and attach.todektop
# execute query with the new sub key
            set for_temp "\\$for_temp"
            set my_query1 $my_query$for_temp
# grep on the values you want
            set att_x [registry get $my_query1 Attach.ToDesktop] 
#if attach.todesktop != 1, then this screen is not used, ignore it
#else check if this is the main screen (relative x == 0)
            if {$att_x != 1} {
                break
            }
            set monitorList ""
            set rel_x [registry get $my_query1 Attach.RelativeX]
            lappend monitorList $rel_x
            set rel_y [registry get $my_query1 Attach.RelativeY]
            lappend monitorList $rel_y
            set res_x [registry get $my_query1 DefaultSettings.XResolution]
            lappend monitorList $res_x
            set res_y [registry get $my_query1 DefaultSettings.YResolution]
            lappend monitorList $res_y
            lappend resultList $monitorList
      }
   }
    return $resultList
 } else {
  puts "This Function is not supported in Linux"
  return $resultList
 }
}
set x [getMonitorsInfo]
puts $x

oehhar added on 2015-09-14 09:00:52:

Thank you for the revised version. The initial exec to query the registry should also be done by the registry package...

Thank you, Harald


anonymous (claiming to be New verison of the code reducing the number of "exec" calls) added on 2015-09-14 07:52:34:
#This Function return Info about all the Monitors connected in Case of Windows Platform
#Function Input  : None
#Function Output : list of lists, each sub list contains info about one of the screens.

#Example :
#Assume you have two screens, the output will look like {{0x0 0x0 0x690 0x41a} {0xfffffb00 0x0 0x500 0x400}}
#The sub list contains in order relativeX , realtiveY , resolutionX , resolutionY for each screen
#For primary screen relativeX and relativeY should be Zeros, and the relativeX and relativeY of the other screen is related to it.
#In the above case the primary screen is on the right, thats why the relativeX of the second screen is negative.
#The resolutionX and resolutionY is the resolution of each screen.
# Note that i grep these values from the registry thats why they appear in Hex.

#By comparing the relativeX and relativeY values of all the screens, you should be
#able to easily allocate your primary screen and the location of other screens with respect to it.

#Function was tested under Tcl8.4 under cygwin (Windows XP).
#Function was tested under Tcl8.4 under Linux  (RH4).
#Function was tested under Tcl8.5 Active state (Windows 7).
#Function was tested under Tcl8.5 under cygwin (Windows 7).


proc getMonitorsInfo {} {
 global tcl_platform
 set resultList ""
 if {$tcl_platform(os) != "Linux"} {
# get the number of monitors
   set monitors [exec reg query {HKLM\SYSTEM\CurrentControlSet\services\monitor\Enum }]
   set monitors [split $monitors "\n" ]
   set monitors [lsearch -inline $monitors *Count* ]
   set monitors [split $monitors " " ]
   set monitors [lindex $monitors end]
# search the registry to get the screens resolutions and find the main screen
   set my_query "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO\\"
   set tmp [exec reg query $my_query]
# split the result into lines to loop on them
   set tmp [split $tmp "\n"] 
#loop to find all under \\VIDEO
   for {set it 0} {$it < [llength $tmp] } {incr it} {
        set my_query "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Hardware\ Profiles\\UnitedVideo\\CONTROL\\VIDEO\\"
# get the line
        set for_tmp [lindex $tmp $it]
        #puts $for_tmp
# split the line by backslashes
        set for_tmp [split $for_tmp "\\"] 
# get the last item after splitting,will be like "{21B5061B-2F71-4D25-8BB5-5CD5A8833140}"        
        set for_tmp [lindex $for_tmp end] 
#make sure empty strings in the list are not used
        if { [string length $for_tmp] < 10 } {
         continue
        }
        #puts $for_tmp
# concat the original query with the new part ,and execute it
         set my_query $my_query$for_tmp 
         set temp [exec reg query $my_query]
# split again to loop on entries of the sub key line by line
        set temp [split $temp "\n"] 
        #puts $temp
#loop to find all under each sub key of \\VIDEO
        for {set i 0} {$i < [llength $temp]} {incr i} {
# get each line
            set for_temp [lindex $temp $i ] 
          #puts $for_temp
# make sure empty strings in the list are not used
     if { [string length $for_temp] < 10 } {
             continue
     }
# get the values of resolution x , resolution y , relative x , and attach.todektop
# execute query with the new sub key
     set for_temp [exec reg query $for_temp] 
# grep on the values you want
     set att_x [split $for_temp "\n"] 
     set att_x [lsearch -inline $att_x *Attach.ToDesktop*]
     set att_x [split $att_x " "]
     set att_x [lindex $att_x end]
#if attach.todesktop != 1, then this screen is not used, ignore it
#else check if this is the main screen (relative x == 0)
     if {$att_x != "0x01"} {
      break
     }
     set monitorList ""
     set rel_x [split $for_temp "\n"]
     set rel_x [lsearch -inline $rel_x *Attach.RelativeX*]
     set rel_x [split $rel_x " "]
     set rel_x [lindex $rel_x end]
     lappend monitorList $rel_x
     set rel_y [split $for_temp "\n"]
     set rel_y [lsearch -inline $rel_y *Attach.RelativeY*]
     set rel_y [split $rel_y " "]
     set rel_y [lindex $rel_y end]
     lappend monitorList $rel_y
     set res_x [split $for_temp "\n"]
     set res_x [lsearch -inline $res_x *DefaultSettings.XResolution*]
     set res_x [split $res_x " "]
     set res_x [lindex $res_x end]
     lappend monitorList $res_x
     set res_y [split $for_temp "\n"]
     set res_y [lsearch -inline $res_y *DefaultSettings.YResolution*]
     set res_y [split $res_y " "]
     set res_y [lindex $res_y end]
     lappend monitorList $res_y
     lappend resultList $monitorList
      }
   }
    return $resultList
 } else {
  puts "This Function is not supported in Linux"
  return $resultList
 }
}
set x [getMonitorsInfo]
puts $x

oehhar added on 2015-09-14 07:00:20:

Thank you for the proposal, looks promissing. It would we great to have this in Tk itself.

I will look to integrate this in BWidget.

Your function requires a bit rewrite to not use exec for performance.

Thank you, Harald