Originally created by @EBNull on GitHub (Dec 20, 2023).
Hi, I noticed issues like #364, #275, and thought I could add some additional context.
I think your solution in #275 will work most of the time (as I think it relies on GDI's virtual screen coordinates), though if you want to uniquely identify physical monitors, I have some pointers for you.
In short, using a combination of GDI, SetupDi, EDD_GET_DEVICE_INTERFACE_NAME , and a little bit of EDID decoding, you can map these around.
This is probably more in-depth than needed (since komorebi is really exclusively in the "virtual screen" layer of abstraction) since you now remember monitors by virtual position, but I thought you might appreciate the additional context.
Originally created by @EBNull on GitHub (Dec 20, 2023).
Hi, I noticed issues like #364, #275, and thought I could add some additional context.
I think your solution in #275 will work most of the time (as I think it relies on [GDI's virtual screen](https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen) coordinates), though if you want to uniquely identify physical monitors, I have some pointers for you.
I was working on a map-virtual-to-physical monitor problem in the past and really found only the `EDID` was persistent and unique enough to identify monitors. I wrote up my journey at https://gist.github.com/EBNull/d65bacceefc58f5f1d728a66039807d2 .
In short, using a combination of `GDI`, `SetupDi`, `EDD_GET_DEVICE_INTERFACE_NAME `, and a little bit of `EDID` decoding, you can map these around.
This is probably more in-depth than needed (since `komorebi` is really exclusively in the "virtual screen" layer of abstraction) since you now remember monitors by virtual position, but I thought you might appreciate the additional context.
And thanks for referencing my gist in https://github.com/LGUG2Z/komorebi/commit/e04ba0e033c4515095d1290019b21bec7a644204 ! It was really neat to stumble across this project, read a few commits and issues, and find my old gist helping someone out.
@LGUG2Z commented on GitHub (Dec 23, 2023):
Thanks so much for sharing this, and for the gist about programmatically changing focus!
I have started slowly working on assigning EDIDs to monitors in `komorebi` if anyone wants to follow along on YouTube: https://www.youtube.com/watch?v=N_IL53wYcXs
Thanks for recording! Watching that reminded me of the similar quagmire I found myself in clicking through those docs - I forgot how bad it was.
I added some small notes to the linked commit above.
General thoughts:
I would have tried for a PR, but I don't know Rust :)
The "other code" I make reference to in the gist is actually (really bad) golang - I wrote the FFIs, and cut a bunch of corners (zero instead of null, etc) :)
I don't recommend trying to parse the device instance string nor doing an enum on those registry keys - the SetupDi family gets you to the right key
@EBNull commented on GitHub (Dec 24, 2023):
Thanks for recording! Watching that reminded me of the similar quagmire I found myself in clicking through those docs - I forgot how bad it was.
I added some small notes to the linked commit above.
General thoughts:
- I would have tried for a PR, but I don't know Rust :)
- The "other code" I make reference to in the gist is actually (really bad) golang - I wrote the FFIs, and cut a bunch of corners (zero instead of null, etc) :)
- I don't recommend trying to parse the device instance string nor doing an enum on those registry keys - the `SetupDi` family gets you to the right key
@EBNull commented on GitHub (Dec 24, 2023):
I started writing more here, but then I figured, eh, have some code. I assume you share the same username on gitlab, so, see https://gitlab.com/ebnull/hudctl/-/blob/master/monman/collectmoninfo_windows.go and https://gitlab.com/ebnull/hudctl/-/blob/master/monman/monitors_windows.go .
// +build windowspackagemonmanimport("fmt""github.com/davecgh/go-spew/spew""gitlab.com/ebnull/hudctl/monman/win32""strings"//"golang.org/x/sys/windows/registry")const(DISPLAY_DEVICE_ACTIVE=0x00000001DISPLAY_DEVICE_PRIMARY_DEVICE=0x00000004DISPLAY_DEVICE_MIRRORING_DRIVER=0x00000008DISPLAY_DEVICE_VGA_COMPATIBLE=0x00000010DISPLAY_DEVICE_REMOVABLE=0x00000020DISPLAY_DEVICE_MODESPRUNED=0x08000000)funcinit(){MonMan=&WindowsMonitorManager{}}typeWindowsMonitorManagerstruct{}func(wmm*WindowsMonitorManager)GetMonitors()([]BasicMonitor,error){ret:=[]BasicMonitor{}seen:=map[string]struct{}{}cmis,err:=CollectWindowsAdapterAndMonitorInfo()iferr!=nil{returnret,err}for_,cmi:=rangecmis{for_,ddinfo:=rangecmi.DisplayDevices{ifddinfo.Monitor==nil{// Enabled monitor but not configured?spew.Dump(ddinfo)// TODO: Expose this issuecontinue}ifDISPLAY_DEVICE_ACTIVE&ddinfo.Monitor.StateFlags==0{continue}ifddinfo.Monitor.DeviceName==""{continue}_,ok:=seen[ddinfo.Monitor.DeviceName]ifok{continue}seen[ddinfo.Monitor.DeviceName]=struct{}{}regPath,edidBytes,err:=GetMonitorRegPathAndEdid(ddinfo.MonitorDeviceInterfaceName)iferr!=nil{returnret,err}regPath=strings.Replace(regPath,"\\REGISTRY\\MACHINE","HKEY_LOCAL_MACHINE",1)vd,err:=NewVendorDataFromEdid(edidBytes)iferr!=nil{returnret,err}hd:=HostData{PhysicalPath:ddinfo.Monitor.DeviceName,LogicalPath:ddinfo.MonitorDeviceInterfaceName,LogicalMonitorIdentity:fmt.Sprintf("%s (%s%04X) %s",vd.Name,vd.ManufacturerId,vd.ProductCode,vd.SerialNumber),}mon:=WindowsMonitor{HostData:hd,VendorData:vd,RegPath:regPath,DisplayMonitorInfoAugmented:cmi.DisplayMonitorInfoAugmented,PhysicalMonitors:cmi.PhysicalMonitors,DDInfo:ddinfo}ret=append(ret,mon)}}//fmt.Printf("hMonitor %d @ (%d, %d) [%s] - %s\n", i, m.Rectangle.Left, m.Rectangle.Top, mie, pma.PhysicalMonitor)//pma.Close()returnret,nil}typeWindowsMonitorstruct{HostDataVendorDataRegPathstringDisplayMonitorInfoAugmentedDisplayMonitorInfoAugmentedPhysicalMonitors*win32.PhysicalMonitorArrayDDInfowin32.AdapterMonitorDisplayDevices}func(wmWindowsMonitor)GetHostData()HostData{returnwm.HostData}func(wmWindowsMonitor)GetVendorData()VendorData{returnwm.VendorData}
EDID stuff:
packagemonmanimport("fmt""gitlab.com/ebnull/hudctl/monman/win32"//"github.com/davecgh/go-spew/spew"//"github.com/winlabs/gowin32"//w "github.com/winlabs/gowin32/wrappers""golang.org/x/sys/windows/registry""strings")funcGetMonitorRegPathAndEdid(monitorPathstring)(string,[]byte,error){monitorPath=strings.ToLower(monitorPath)//fmt.Printf("Looking for %s\n", spew.Sdump(monitorPath))hDevInfo,err:=win32.SetupDiGetClassDevsEx(win32.GUID_DEVINTERFACE_MONITOR,"",0,win32.DIGCF_DEVICEINTERFACE,0,"")iferr!=nil{return"",[]byte{},err}//fmt.Printf("Got hDevInfo: %#v\n", hDevInfo)devpath:=""i:=0fori=0;true;i++{difd,err:=win32.SetupDiEnumDeviceInterfaces(hDevInfo,0,win32.GUID_DEVINTERFACE_MONITOR,i)ifdifd==nil&&err==nil{break}//fmt.Printf(spew.Sdump(difd))iferr!=nil{return"",[]byte{},err}devpath,err=win32.SetupDiGetDeviceInterfaceDetail(hDevInfo,difd)iferr!=nil{return"",[]byte{},err}ifstrings.ToLower(devpath)==monitorPath{break}}ifstrings.ToLower(devpath)!=monitorPath{return"",[]byte{},fmt.Errorf("Could not find %s in SetupApi",monitorPath)}did,err:=win32.SetupDiEnumDeviceInfo(hDevInfo,i)iferr!=nil{return"",[]byte{},err}hKey,err:=win32.SetupDiOpenDevRegKey(hDevInfo,did,win32.DICS_FLAG_GLOBAL,0,win32.DIREG_DEV,win32.KEY_READ)iferr!=nil{return"",[]byte{},err}regkey:=registry.Key(hKey)edidBytes,_,err:=regkey.GetBinaryValue("EDID")iferr!=nil{return"",[]byte{},err}keyName,err:=win32.NtQueryKeyName(hKey)iferr!=nil{return"",[]byte{},err}returnkeyName,edidBytes,err}
@EBNull commented on GitHub (Dec 24, 2023):
Additional pointers (in no specific order):
- gowin32 `GetAllDisplayDevices` - https://github.com/winlabs/gowin32/blob/0d265587d3c90d3dca04c4927c845b5334ade86f/sysinfo.go#L70-L92
- gowin32 `GetAllDisplayMonitors` - https://github.com/winlabs/gowin32/blob/0d265587d3c90d3dca04c4927c845b5334ade86f/sysinfo.go#L105C6-L119
My own code, "augmented" structures and a combination function:
```go
type DisplayMonitorInfoAugmented struct {
DisplayMonitorInfo gowin32.DisplayMonitorInfo
MonitorInfoEx *win32.MONITORINFOEXW
}
type PhysicalMonitorInfoAugmented struct {
DisplayDevices []win32.AdapterMonitorDisplayDevices
PhysicalMonitors *win32.PhysicalMonitorArray
}
type CollectedWindowsAdapterInfo struct {
DisplayMonitorInfoAugmented
PhysicalMonitorInfoAugmented
}
func CollectWindowsAdapterAndMonitorInfo() ([]CollectedWindowsAdapterInfo, error) {
dmmap := map[string]gowin32.DisplayDevice{}
for _, dd := range gowin32.GetAllDisplayDevices() {
dmmap[dd.DeviceName] = dd
}
var err error
ret := []CollectedWindowsAdapterInfo{}
for _, dm := range gowin32.GetAllDisplayMonitors() {
cmi := CollectedWindowsAdapterInfo{DisplayMonitorInfoAugmented: DisplayMonitorInfoAugmented{DisplayMonitorInfo: dm}}
cmi.MonitorInfoEx, err = win32.GetMonitorInfo(windows.Handle(dm.Handle))
if err != nil {
return nil, err
}
cmi.PhysicalMonitors, err = win32.GetPhysicalMonitorsFromHMONITOR(windows.Handle(dm.Handle))
if err != nil {
return nil, err
}
cmi.DisplayDevices = win32.GetDisplayDevicesFromAdapterDeviceName(cmi.MonitorInfoEx.Device())
ret = append(ret, cmi)
}
return ret, nil
}
```
Usage of the data to get the EDIDs:
```go
// +build windows
package monman
import (
"fmt"
"github.com/davecgh/go-spew/spew"
"gitlab.com/ebnull/hudctl/monman/win32"
"strings"
//"golang.org/x/sys/windows/registry"
)
const (
DISPLAY_DEVICE_ACTIVE = 0x00000001
DISPLAY_DEVICE_PRIMARY_DEVICE = 0x00000004
DISPLAY_DEVICE_MIRRORING_DRIVER = 0x00000008
DISPLAY_DEVICE_VGA_COMPATIBLE = 0x00000010
DISPLAY_DEVICE_REMOVABLE = 0x00000020
DISPLAY_DEVICE_MODESPRUNED = 0x08000000
)
func init() {
MonMan = &WindowsMonitorManager{}
}
type WindowsMonitorManager struct{}
func (wmm *WindowsMonitorManager) GetMonitors() ([]BasicMonitor, error) {
ret := []BasicMonitor{}
seen := map[string]struct{}{}
cmis, err := CollectWindowsAdapterAndMonitorInfo()
if err != nil {
return ret, err
}
for _, cmi := range cmis {
for _, ddinfo := range cmi.DisplayDevices {
if ddinfo.Monitor == nil {
// Enabled monitor but not configured?
spew.Dump(ddinfo)
// TODO: Expose this issue
continue
}
if DISPLAY_DEVICE_ACTIVE&ddinfo.Monitor.StateFlags == 0 {
continue
}
if ddinfo.Monitor.DeviceName == "" {
continue
}
_, ok := seen[ddinfo.Monitor.DeviceName]
if ok {
continue
}
seen[ddinfo.Monitor.DeviceName] = struct{}{}
regPath, edidBytes, err := GetMonitorRegPathAndEdid(ddinfo.MonitorDeviceInterfaceName)
if err != nil {
return ret, err
}
regPath = strings.Replace(regPath, "\\REGISTRY\\MACHINE", "HKEY_LOCAL_MACHINE", 1)
vd, err := NewVendorDataFromEdid(edidBytes)
if err != nil {
return ret, err
}
hd := HostData{
PhysicalPath: ddinfo.Monitor.DeviceName,
LogicalPath: ddinfo.MonitorDeviceInterfaceName,
LogicalMonitorIdentity: fmt.Sprintf("%s (%s%04X) %s", vd.Name, vd.ManufacturerId, vd.ProductCode, vd.SerialNumber),
}
mon := WindowsMonitor{HostData: hd, VendorData: vd, RegPath: regPath, DisplayMonitorInfoAugmented: cmi.DisplayMonitorInfoAugmented, PhysicalMonitors: cmi.PhysicalMonitors, DDInfo: ddinfo}
ret = append(ret, mon)
}
}
//fmt.Printf("hMonitor %d @ (%d, %d) [%s] - %s\n", i, m.Rectangle.Left, m.Rectangle.Top, mie, pma.PhysicalMonitor)
//pma.Close()
return ret, nil
}
type WindowsMonitor struct {
HostData
VendorData
RegPath string
DisplayMonitorInfoAugmented DisplayMonitorInfoAugmented
PhysicalMonitors *win32.PhysicalMonitorArray
DDInfo win32.AdapterMonitorDisplayDevices
}
func (wm WindowsMonitor) GetHostData() HostData {
return wm.HostData
}
func (wm WindowsMonitor) GetVendorData() VendorData {
return wm.VendorData
}
```
EDID stuff:
```go
package monman
import (
"fmt"
"gitlab.com/ebnull/hudctl/monman/win32"
//"github.com/davecgh/go-spew/spew"
//"github.com/winlabs/gowin32"
//w "github.com/winlabs/gowin32/wrappers"
"golang.org/x/sys/windows/registry"
"strings"
)
func GetMonitorRegPathAndEdid(monitorPath string) (string, []byte, error) {
monitorPath = strings.ToLower(monitorPath)
//fmt.Printf("Looking for %s\n", spew.Sdump(monitorPath))
hDevInfo, err := win32.SetupDiGetClassDevsEx(win32.GUID_DEVINTERFACE_MONITOR, "", 0, win32.DIGCF_DEVICEINTERFACE, 0, "")
if err != nil {
return "", []byte{}, err
}
//fmt.Printf("Got hDevInfo: %#v\n", hDevInfo)
devpath := ""
i := 0
for i = 0; true; i++ {
difd, err := win32.SetupDiEnumDeviceInterfaces(hDevInfo, 0, win32.GUID_DEVINTERFACE_MONITOR, i)
if difd == nil && err == nil {
break
}
//fmt.Printf(spew.Sdump(difd))
if err != nil {
return "", []byte{}, err
}
devpath, err = win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, difd)
if err != nil {
return "", []byte{}, err
}
if strings.ToLower(devpath) == monitorPath {
break
}
}
if strings.ToLower(devpath) != monitorPath {
return "", []byte{}, fmt.Errorf("Could not find %s in SetupApi", monitorPath)
}
did, err := win32.SetupDiEnumDeviceInfo(hDevInfo, i)
if err != nil {
return "", []byte{}, err
}
hKey, err := win32.SetupDiOpenDevRegKey(hDevInfo, did, win32.DICS_FLAG_GLOBAL, 0, win32.DIREG_DEV, win32.KEY_READ)
if err != nil {
return "", []byte{}, err
}
regkey := registry.Key(hKey)
edidBytes, _, err := regkey.GetBinaryValue("EDID")
if err != nil {
return "", []byte{}, err
}
keyName, err := win32.NtQueryKeyName(hKey)
if err != nil {
return "", []byte{}, err
}
return keyName, edidBytes, err
}
```
Oh, this seems related to my question here https://github.com/LGUG2Z/komorebi/discussions/657, but I'm not sure if the solution implemented is applicable to my case? Is there a way now I can pin a given monitor workspace to a specific monitor when I change which monitors are plugged in to my laptop?
@gazpachoking commented on GitHub (Feb 8, 2024):
Oh, this seems related to my question here https://github.com/LGUG2Z/komorebi/discussions/657, but I'm not sure if the solution implemented is applicable to my case? Is there a way now I can pin a given monitor workspace to a specific monitor when I change which monitors are plugged in to my laptop?
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @EBNull on GitHub (Dec 20, 2023).
Hi, I noticed issues like #364, #275, and thought I could add some additional context.
I think your solution in #275 will work most of the time (as I think it relies on GDI's virtual screen coordinates), though if you want to uniquely identify physical monitors, I have some pointers for you.
I was working on a map-virtual-to-physical monitor problem in the past and really found only the
EDIDwas persistent and unique enough to identify monitors. I wrote up my journey at https://gist.github.com/EBNull/d65bacceefc58f5f1d728a66039807d2 .In short, using a combination of
GDI,SetupDi,EDD_GET_DEVICE_INTERFACE_NAME, and a little bit ofEDIDdecoding, you can map these around.This is probably more in-depth than needed (since
komorebiis really exclusively in the "virtual screen" layer of abstraction) since you now remember monitors by virtual position, but I thought you might appreciate the additional context.And thanks for referencing my gist in https://github.com/LGUG2Z/komorebi/commit/e04ba0e033c4515095d1290019b21bec7a644204 ! It was really neat to stumble across this project, read a few commits and issues, and find my old gist helping someone out.
@LGUG2Z commented on GitHub (Dec 23, 2023):
Thanks so much for sharing this, and for the gist about programmatically changing focus!
I have started slowly working on assigning EDIDs to monitors in
komorebiif anyone wants to follow along on YouTube: https://www.youtube.com/watch?v=N_IL53wYcXs@EBNull commented on GitHub (Dec 24, 2023):
Thanks for recording! Watching that reminded me of the similar quagmire I found myself in clicking through those docs - I forgot how bad it was.
I added some small notes to the linked commit above.
General thoughts:
SetupDifamily gets you to the right key@EBNull commented on GitHub (Dec 24, 2023):
I started writing more here, but then I figured, eh, have some code. I assume you share the same username on gitlab, so, see https://gitlab.com/ebnull/hudctl/-/blob/master/monman/collectmoninfo_windows.go and https://gitlab.com/ebnull/hudctl/-/blob/master/monman/monitors_windows.go .
@EBNull commented on GitHub (Dec 24, 2023):
Additional pointers (in no specific order):
GetAllDisplayDevices- https://github.com/winlabs/gowin32/blob/0d265587d3c90d3dca04c4927c845b5334ade86f/sysinfo.go#L70-L92GetAllDisplayMonitors- https://github.com/winlabs/gowin32/blob/0d265587d3c90d3dca04c4927c845b5334ade86f/sysinfo.go#L105C6-L119My own code, "augmented" structures and a combination function:
Usage of the data to get the EDIDs:
EDID stuff:
@gazpachoking commented on GitHub (Feb 8, 2024):
Oh, this seems related to my question here https://github.com/LGUG2Z/komorebi/discussions/657, but I'm not sure if the solution implemented is applicable to my case? Is there a way now I can pin a given monitor workspace to a specific monitor when I change which monitors are plugged in to my laptop?