I'd like to incorporate a way to have my GUI open on a second monitor, if available. I want to add in some type of error handling so if there is a second monitor, use it, else open on the center of the detected display. Can this be done?
I've been using this snippet using the win32 API to enumerate monitors on windows:
import ctypes
user = ctypes.windll.user32
class RECT(ctypes.Structure):
_fields_ = [
('left', ctypes.c_long),
('top', ctypes.c_long),
('right', ctypes.c_long),
('bottom', ctypes.c_long)
]
def dump(self):
return [int(val) for val in (self.left, self.top, self.right, self.bottom)]
class MONITORINFO(ctypes.Structure):
_fields_ = [
('cbSize', ctypes.c_ulong),
('rcMonitor', RECT),
('rcWork', RECT),
('dwFlags', ctypes.c_ulong)
]
def get_monitors():
retval = []
CBFUNC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong, ctypes.POINTER(RECT), ctypes.c_double)
def cb(hMonitor, hdcMonitor, lprcMonitor, dwData):
r = lprcMonitor.contents
#print("cb: %s %s %s %s %s %s %s %s" % (hMonitor, type(hMonitor), hdcMonitor, type(hdcMonitor), lprcMonitor, type(lprcMonitor), dwData, type(dwData)))
data = [hMonitor]
data.append(r.dump())
retval.append(data)
return 1
cbfunc = CBFUNC(cb)
temp = user.EnumDisplayMonitors(0, 0, cbfunc, 0)
#print(temp)
return retval
def monitor_areas():
retval = []
monitors = get_monitors()
for hMonitor, extents in monitors:
data = [hMonitor]
mi = MONITORINFO()
mi.cbSize = ctypes.sizeof(MONITORINFO)
mi.rcMonitor = RECT()
mi.rcWork = RECT()
res = user.GetMonitorInfoA(hMonitor, ctypes.byref(mi))
data = mi.rcMonitor.dump()
# data.append(mi.rcWork.dump())
retval.append(data)
return retval
if __name__ == "__main__":
print(monitor_areas())
On my system that prints
[[0, 0, 3440, 1440], [3440, 0, 5120, 1050]]
In tkinter you can then move your app window to a given location with
appwindow.geometry('+1720+720')
Which puts it 1720px in from the left and 720 down from the top.
Any monitors that are to the left of or above your primary monitor may get negative coordinates, but they should work just fine.
On MacOS, use Appkit:
import AppKit
[(screen.frame().size.width, screen.frame().size.height)
for screen in AppKit.NSScreen.screens()]
will give you a list of tuples containing all screen sizes (if multiple monitors present)
Related
I'm trying to create a new Windows window by calling the win32gui.CreateWindow Function. The Window is created as expected, however the title is made up of Chinese (or other similar) characters, instead of reading "Hello, World!".
By counting the number of characters and reading documentation, I guessed that windows is using double width characters and python is not. Here are some of the things I tried to make it display properly, none of these attempts worked however:
def to_16(s):
s = s.encode('utf-16')
return "".join([format(i, "X").rjust(2,"0") for i in s])
def to_16(s):
return ''.join([f'{c}{chr(32)}' for c in s])
def to_16(s):
return ''.join([chr(i) for i in s.encode('utf-16')])
This is the code I use to create the window:
import ctypes
import cython
import win32, win32gui, win32ui, win32con, win32api
LRESULT = ctypes.c_longlong
WNDPROC = ctypes.CFUNCTYPE(LRESULT,
ctypes.wintypes.HWND,
ctypes.c_uint,
ctypes.wintypes.WPARAM,
ctypes.wintypes.LPARAM)
#WNDPROC
def WindowProc(hWnd:ctypes.wintypes.HWND,
uMsg:ctypes.c_uint,
wParam:ctypes.wintypes.WPARAM,
lParam:ctypes.wintypes.LPARAM)->LRESULT:
if uMsg == win32con.WM_PAINT:
hDC, paintStruct = win32gui.BeginPaint(hWnd)
rect = win32gui.GetClientRect(hWnd)
win32gui.DrawText(hDC,
'Hello send by Python via Win32!',
-1,
rect,
win32con.DT_SINGLELINE | win32con.DT_CENTER | win32con.DT_VCENTER)
win32gui.EndPaint(hWnd, paintStruct)
return 0
elif uMsg == win32con.WM_DESTROY:
print('Window: destroyed')
win32gui.PostQuitMessage(0)
return 0
else:
return win32gui.DefWindowProc(hWnd, uMsg, wParam, lParam)
HCURSOR = ctypes.wintypes.HICON
class WNDCLASSEX(ctypes.Structure): # tagWNDCLASSEXA
_fields_ = [("cbSize" , ctypes.c_uint),
("style" , ctypes.c_uint),
("lpfnWndProc" , WNDPROC),
("cbClsExtra" , ctypes.c_int),
("cbWndExtra" , ctypes.c_int),
("hInstance" , ctypes.wintypes.HINSTANCE),
("hIcon" , ctypes.wintypes.HICON),
("hCursor" , HCURSOR),
("hbrBackground", ctypes.wintypes.HBRUSH),
("lpszMenuName" , ctypes.wintypes.LPCWSTR),
("lpszClassName", ctypes.wintypes.LPCWSTR),
("hIconSm" , ctypes.wintypes.HICON),]
def WinMain():
print('Start...')
hInstance = win32api.GetModuleHandle()
hPrevInstance = None
lpCmdLine = ''
nCmdShow = win32con.SW_SHOWNORMAL
szWindowClass = 'DesktopApp' # The main window class name.
szTitle = 'Hello, World!' # The string that appears in the application's title bar.
wcex = WNDCLASSEX()
wcex.cbSize = ctypes.sizeof(WNDCLASSEX)
wcex.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
wcex.lpfnWndProc = WindowProc
wcex.cbClsExtra = 0
wcex.cbWndExtra = 0
wcex.hInstance = hInstance
wcex.hIcon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
wcex.hCursor = win32api.LoadCursor(None, win32con.IDC_ARROW)
wcex.hbrBackground = ctypes.wintypes.HBRUSH(win32con.COLOR_WINDOW+1)
wcex.lpszMenuName = None
wcex.lpszClassName = szWindowClass
wcex.hIconSm = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
print('Setup: done')
wndClassAtom = 0
try:
wndClassAtom = ctypes.windll.user32.RegisterClassExA(wcex)
print(f'wndClassAtom: {wndClassAtom}')
except Exception as e:
print(e)
finally:
if wndClassAtom == 0:
print('Call to RegisterClassEx failed!')
return 1
print('Class: registered')
hInst = hInstance
hWnd = 0
try:
hWnd = win32gui.CreateWindow(wndClassAtom,
to_16(szTitle),
win32con.WS_OVERLAPPEDWINDOW,
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
500, 100,
0,
0,
hInstance,
None)
print(f'hWnd: {hWnd}')
except Exception as e:
print(e)
finally:
if not hWnd:
win32gui.UnregisterClass(wndClassAtom, hInstance)
print('Call to CreateWindow failed!')
return 1
print('Window: created')
win32gui.ShowWindow(hWnd,
nCmdShow)
win32gui.UpdateWindow(hWnd)
# Dispatch messages
win32gui.PumpMessages()
win32gui.UnregisterClass(wndClassAtom, hInstance)
return 0
errCode = WinMain()
if errCode == 1: print('----------WINDOWS ERROR----------')
I'd love for a robust way to pass python strings to the Windows API.
I found the code below which is supposed to programmatically change the console font size. I'm on Windows 10.
However, whatever values I tweak, I can't seem to get any control over the font size, and also for some reason the console which gets opened when I run this script is very wide.
I have no idea how ctypes works - all I want is to modify the size of the console font from inside Python.
Any actual working solutions?
import ctypes
LF_FACESIZE = 32
STD_OUTPUT_HANDLE = -11
class COORD(ctypes.Structure):
_fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]
class CONSOLE_FONT_INFOEX(ctypes.Structure):
_fields_ = [("cbSize", ctypes.c_ulong),
("nFont", ctypes.c_ulong),
("dwFontSize", COORD),
("FontFamily", ctypes.c_uint),
("FontWeight", ctypes.c_uint),
("FaceName", ctypes.c_wchar * LF_FACESIZE)]
font = CONSOLE_FONT_INFOEX()
font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX)
font.nFont = 12
font.dwFontSize.X = 11
font.dwFontSize.Y = 18
font.FontFamily = 54
font.FontWeight = 400
font.FaceName = "Lucida Console"
handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
ctypes.windll.kernel32.SetCurrentConsoleFontEx(
handle, ctypes.c_long(False), ctypes.pointer(font))
print("Foo")
Before everything, check [SO]: C function called from Python via ctypes returns incorrect value (#CristiFati's answer) for a common pitfall when working with CTypes (calling functions).
The CTypes home page (also listed in the above URL): [Python.Docs]: ctypes - A foreign function library for Python
I changed your code "a bit".
code00.py:
#!/usr/bin/env python
import ctypes as cts
import ctypes.wintypes as wts
import sys
LF_FACESIZE = 32
STD_OUTPUT_HANDLE = -11
class CONSOLE_FONT_INFOEX(cts.Structure):
_fields_ = (
("cbSize", wts.ULONG),
("nFont", wts.DWORD),
("dwFontSize", wts._COORD),
("FontFamily", wts.UINT),
("FontWeight", wts.UINT),
("FaceName", wts.WCHAR * LF_FACESIZE)
)
def main(*argv):
kernel32 = cts.WinDLL("Kernel32.dll")
GetLastError = kernel32.GetLastError
GetLastError.argtypes = ()
GetLastError.restype = wts.DWORD
GetStdHandle = kernel32.GetStdHandle
GetStdHandle.argtypes = (wts.DWORD,)
GetStdHandle.restype = wts.HANDLE
GetCurrentConsoleFontEx = kernel32.GetCurrentConsoleFontEx
GetCurrentConsoleFontEx.argtypes = (wts.HANDLE, wts.BOOL, cts.POINTER(CONSOLE_FONT_INFOEX))
GetCurrentConsoleFontEx.restype = wts.BOOL
SetCurrentConsoleFontEx = kernel32.SetCurrentConsoleFontEx
SetCurrentConsoleFontEx.argtypes = (wts.HANDLE, wts.BOOL, cts.POINTER(CONSOLE_FONT_INFOEX))
SetCurrentConsoleFontEx.restype = wts.BOOL
# Get stdout handle
stdout = GetStdHandle(STD_OUTPUT_HANDLE)
if not stdout:
print("{:s} error: {:d}".format(GetStdHandle.__name__, GetLastError()))
return
# Get current font characteristics
font = CONSOLE_FONT_INFOEX()
font.cbSize = cts.sizeof(CONSOLE_FONT_INFOEX)
res = GetCurrentConsoleFontEx(stdout, False, cts.byref(font))
if not res:
print("{:s} error: {:d}".format(GetCurrentConsoleFontEx.__name__, GetLastError()))
return
# Display font information
print("Console information for {:}".format(font))
for field_name, _ in font._fields_:
field_data = getattr(font, field_name)
if field_name == "dwFontSize":
print(" {:s}: {{X: {:d}, Y: {:d}}}".format(field_name, field_data.X, field_data.Y))
else:
print(" {:s}: {:}".format(field_name, field_data))
while 1:
try:
height = int(input("\nEnter font height (invalid to exit): "))
except:
break
# Alter font height
font.dwFontSize.X = 10 # Changing X has no effect (at least on my machine)
font.dwFontSize.Y = height
# Display font information
res = SetCurrentConsoleFontEx(stdout, False, cts.byref(font))
if not res:
print("{:s} error: {:d}".format(SetCurrentConsoleFontEx.__name__, GetLastError()))
return
print("OMG! The window changed :)")
# Get current font characteristics again
res = GetCurrentConsoleFontEx(stdout, False, cts.byref(font))
if not res:
print("{:s} error: {:d}".format(GetCurrentConsoleFontEx.__name__, GetLastError()))
return
print("\nNew sizes X: {:d}, Y: {:d}".format(font.dwFontSize.X, font.dwFontSize.Y))
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Notes:
CTypes allows low level access similar to C (only the syntax is Python)
Code uses [MS.Learn]: SetConsoleTextAttribute function
In order to avoid setting invalid values, it's used in conjunction with its counterpart: [MS.Learn]: GetCurrentConsoleFontEx function. Call that function in order to populate the CONSOLE_FONT_INFOEX structure, then modify only the desired values
There are "simpler" functions (e.g. [MS.Learn]: GetCurrentConsoleFont function or [MS.Learn]: GetConsoleFontSize function), but unfortunately there are no corresponding setters
Note that all referenced WinAPI functions are deprecated
The ctypes.wintypes constants (which reference the standard CTypes types) are used (to give the code a Win like flavor)
It is very (almost painfully) long (also because I've added proper error handling)
An alternative, as suggested in one of the answers of [SO]: Change console font in Windows (where you copied the code from), would be to install a 3rd-party module (e.g. [GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions which is a Python wrapper over WINAPIs) which would require less code to write because the bridging between Python and C would be already implemented, and probably the above functionality could be accomplished in just a few lines
As I commented in the code, setting COORD.X seems to be ignored. But it's automatically set when setting COORD.Y (to a value close to COORD.Y // 2 - probably to preserve the aspect ratio). On my machine (Win 10 pc064) with the currently selected font, the default value is 16. You might want to set it back at the end, to avoid leaving the console in a "challenged" state (apparently, Win adjusts Cmd window size, to be (sort of) in sync with the font size):
It's not a pure python problem, but covers Windows API.
Look at the documentation of CONSOLE_FONT_INFOEX structure. There is a COORD member on it for width and height of each character.
To change console font size, you can give these attributes as a proper value:
font.dwFontSize.X = 11
font.dwFontSize.Y = 18
Reference: Change console font in Windows
For anyone in the future who wants to use the console as a method of displaying (black and white) images or just getting the smallest possible font size, this is the modified answer code I used. I found a height and width of 2px was the best smallest size, but be cautious, all sizes distort the picture, some more than others. As the font, I use the only "rasterfont" that's available. (Personally I had massive problems getting this to work, not sure why tho... and this is NOT the same as the accepted answer)
def changeFontSize(size=2): #Changes the font size to *size* pixels (kind of, but not really. You'll have to try it to chack if it works for your purpose ;) )
from ctypes import POINTER, WinDLL, Structure, sizeof, byref
from ctypes.wintypes import BOOL, SHORT, WCHAR, UINT, ULONG, DWORD, HANDLE
LF_FACESIZE = 32
STD_OUTPUT_HANDLE = -11
class COORD(Structure):
_fields_ = [
("X", SHORT),
("Y", SHORT),
]
class CONSOLE_FONT_INFOEX(Structure):
_fields_ = [
("cbSize", ULONG),
("nFont", DWORD),
("dwFontSize", COORD),
("FontFamily", UINT),
("FontWeight", UINT),
("FaceName", WCHAR * LF_FACESIZE)
]
kernel32_dll = WinDLL("kernel32.dll")
get_last_error_func = kernel32_dll.GetLastError
get_last_error_func.argtypes = []
get_last_error_func.restype = DWORD
get_std_handle_func = kernel32_dll.GetStdHandle
get_std_handle_func.argtypes = [DWORD]
get_std_handle_func.restype = HANDLE
get_current_console_font_ex_func = kernel32_dll.GetCurrentConsoleFontEx
get_current_console_font_ex_func.argtypes = [HANDLE, BOOL, POINTER(CONSOLE_FONT_INFOEX)]
get_current_console_font_ex_func.restype = BOOL
set_current_console_font_ex_func = kernel32_dll.SetCurrentConsoleFontEx
set_current_console_font_ex_func.argtypes = [HANDLE, BOOL, POINTER(CONSOLE_FONT_INFOEX)]
set_current_console_font_ex_func.restype = BOOL
stdout = get_std_handle_func(STD_OUTPUT_HANDLE)
font = CONSOLE_FONT_INFOEX()
font.cbSize = sizeof(CONSOLE_FONT_INFOEX)
font.dwFontSize.X = size
font.dwFontSize.Y = size
set_current_console_font_ex_func(stdout, False, byref(font))
In Python2.7 with win32, I can easily get other window's toolStrip1 handle through win32gui.EnumChildWindows, however I do not know how to get it's button item control through it's item name, e.g click item button "Load" in toolStrip1.
Can I try to use win32gui.SendMessage(handle, click, "Load"......) or a similar way to achieve this?
I can use rect x,y of toolStrip1 via win32gui.GetWindowRect, and roughly can do click button via x+40, x+80 accordingly, but it is not really good specially when the item position changes in the new version.
Here is the code from a sample and modified with a few other issues:
import win32con
import commctrl, ctypes
from ctypes import *
import sys
import time
class TextBox:
# represent the TBBUTTON structure
# note this is 32 bit, 64 bit padds 4 more reserved bytes
class TBBUTTON(Structure):
_pack_ = 1
_fields_ = [
('iBitmap', c_int),
('idCommand', c_int),
('fsState', c_ubyte),
('fsStyle', c_ubyte),
('bReserved', c_ubyte * 2),
('dwData', c_ulong),
('iString', c_int),
]
class RECT(Structure):
_pack_ = 1
_fields_ = [
('left',c_ulong),
('top',c_ulong),
('right',c_ulong),
('bottom',c_ulong),
]
def run(self):
#init vars
self.count=0
#get the TestApp window
lhWnd = win32gui.FindWindow(None,'TestApp')
print "TestApp handle=",lhWnd
#get the TestApp child window
win32gui.EnumChildWindows(lhWnd, self.EnumChildWindows, 0)
print "toolStrip1 handle=",self.toolbar_hwnd
#focus TestApp window
ctypes.windll.user32.SetFocus(lhWnd)
win32gui.SetForegroundWindow(lhWnd)
win32gui.ShowWindow(lhWnd, win32con.SW_SHOWNORMAL)
#############################################donot work part####################################################
#to get how many item buttons in toolStrip1, it return 0 -- WHY? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
cnt = windll.user32.SendMessageA(self.toolbar_hwnd, commctrl.TB_BUTTONCOUNT, 0, 0)
print "Why button cnt=0?? cnt=",cnt
#and seems this is no affection as well -- Why? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
index=1 #assume we got Load button index from it's name (do not know how?, Load Button index=1 based on 0
win32api.PostMessage(self.toolbar_hwnd,commctrl.TCM_SETCURFOCUS,index,0) #TCM_SETCURSEL
print "Why?? cannot focus on [Load] Button"
time.sleep(3)
#try to get [Load] button's rect, but not work <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
pid = c_ulong();
windll.user32.GetWindowThreadProcessId(self.toolbar_hwnd, byref(pid))
hProcess = windll.kernel32.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, pid)
lpPointer = windll.kernel32.VirtualAllocEx(hProcess, 0, sizeof(TBBUTTON), win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
rlpPointer = windll.kernel32.VirtualAllocEx(hProcess, 0, sizeof(RECT), win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
# init our tool bar button and a handle to it
tbButton = TBBUTTON()
butHandle = c_int()
idx_rect = RECT()
# query the button into the memory we allocated
windll.user32.SendMessageA(self.toolbar_hwnd, commctrl.TB_GETBUTTON, index, lpPointer)
# read the memory into our button struct
windll.kernel32.ReadProcessMemory(hProcess, lpPointer, addressof(tbButton), 20, None)
# read the 1st 4 bytes from the dwData into the butHandle var
# these first 4 bytes contain the handle to the button
windll.kernel32.ReadProcessMemory(hProcess, tbButton.dwData, addressof(butHandle), 4, None)
# get the pid that created the button
butPid = c_ulong()
windll.user32.GetWindowThreadProcessId(butHandle, byref(butPid))
wszBuff = create_unicode_buffer(win32con.MAX_PATH)
windll.kernel32.ReadProcessMemory(hProcess, tbButton.iString, wszBuff, win32con.MAX_PATH, None)
win32api.SendMessage(self.toolbar_hwnd,commctrl.TB_GETRECT,tbButton.idCommand,rlpPointer)
windll.kernel32.ReadProcessMemory(hProcess, rlpPointer, addressof(idx_rect), sizeof(idx_rect), None)
xpos = int((idx_rect.right-idx_rect.left)/2)+idx_rect.left
ypos = int((idx_rect.bottom-idx_rect.top)/2)+idx_rect.top
lParam = ypos<<16 | xpos
print "Why x,y=0?? [Load] button X,Y=",xpos,ypos
###############################################################################################################
#but assume we got button [Load] Rect[10,80] based on toolStrip1, and click it WORKS!
lParam = 10<<16 | 80
win32api.PostMessage(self.toolbar_hwnd,win32con.WM_LBUTTONDOWN,win32con.MK_LBUTTON,lParam)
win32api.PostMessage(self.toolbar_hwnd,win32con.WM_LBUTTONUP,win32con.MK_LBUTTON,lParam)
def EnumChildWindows(self,lhWnd,lParam):
text = win32gui.GetWindowText(lhWnd)
#rect1 = win32gui.GetWindowRect(lhWnd)
if text=="toolStrip1":
self.toolbar_hwnd=lhWnd
rx=TextBox()
rx.run()
This page says:
Enumeration types are not implemented. You can do it easily yourself,
using c_int as the base class.
If it were easy, why isn't it implemented yet? How do I get the current color temperature of my monitor, for instance?
BOOL GetMonitorColorTemperature(
_In_ HANDLE hMonitor,
_Out_ LPMC_COLOR_TEMPERATURE pctCurrentColorTemperature
);
Parameters
hMonitor [in]
Handle to a physical monitor. To get the monitor handle, call GetPhysicalMonitorsFromHMONITOR or GetPhysicalMonitorsFromIDirect3DDevice9.
pctCurrentColorTemperature [out]
Receives the monitor's current color temperature, specified as a member of the MC_COLOR_TEMPERATURE enumeration.
Return value
If the function succeeds, the return value is TRUE. If the function fails, the return value is FALSE. To get extended error information, call GetLastError.
This is the closest i was able to get:
from ctypes import *
import win32api # http://sourceforge.net/projects/pywin32/files/pywin32/
for idx, (hMon, hDC, (left, top, right, bottom)) in enumerate(win32api.EnumDisplayMonitors(None, None)):
print(hMon.handle) # or int(hMon)
class MyEnum(c_int):
MC_COLOR_TEMPERATURE_UNKNOWN = 0
MC_COLOR_TEMPERATURE_4000K = 1
MC_COLOR_TEMPERATURE_5000K = 2
MC_COLOR_TEMPERATURE_6500K = 3
MC_COLOR_TEMPERATURE_7500K = 4
MC_COLOR_TEMPERATURE_8200K = 5
MC_COLOR_TEMPERATURE_9300K = 6
MC_COLOR_TEMPERATURE_10000K = 7
MC_COLOR_TEMPERATURE_11500K = 8
o = MyEnum()
print(o)
po = pointer(o)
t = windll.dxva2.GetMonitorColorTemperature(hMon.handle, po) #byref(o))
#print(dir(o))
print(o)
print(po.contents)
print(t)
print(windll.kernel32.GetLastError()) # ERROR_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE = -1071241844 # Variable c_long '-0x03fd9da74'
...returns this:
65537
65539
<MyEnum object at 0x006F6DA0>
<MyEnum object at 0x006F6DA0>
<MyEnum object at 0x0234FAD0>
0
-1071241844
It's the same for either monitor handle. What am I doing wrong?
Using this answer, I somehow found out that it works using None as physical monitor handle:
from ctypes import *
from ctypes.wintypes import BOOL, HMONITOR, HDC, RECT, LPARAM, DWORD, BYTE, WCHAR, HANDLE
import win32api # http://sourceforge.net/projects/pywin32/files/pywin32/
_MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)
class _PHYSICAL_MONITOR(Structure):
_fields_ = [('handle', HANDLE),
('description', WCHAR * 128)]
def _iter_physical_monitors(close_handles=True):
"""Iterates physical monitors.
The handles are closed automatically whenever the iterator is advanced.
This means that the iterator should always be fully exhausted!
If you want to keep handles e.g. because you need to store all of them and
use them later, set `close_handles` to False and close them manually."""
def callback(hmonitor, hdc, lprect, lparam):
monitors.append(hmonitor)
return True
monitors = []
if not windll.user32.EnumDisplayMonitors(None, None, _MONITORENUMPROC(callback), None):
raise WinError('EnumDisplayMonitors failed')
for monitor in monitors:
# Get physical monitor count
count = DWORD()
if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count)):
raise WinError()
# Get physical monitor handles
physical_array = (_PHYSICAL_MONITOR * count.value)()
if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array):
raise WinError()
for physical in physical_array:
yield physical.handle
if close_handles:
if not windll.dxva2.DestroyPhysicalMonitor(physical.handle):
raise WinError()
mons = [m for m in _iter_physical_monitors(False)]
#for idx, (hMon, hDC, (left, top, right, bottom)) in enumerate(win32api.EnumDisplayMonitors(None, None)):
# print(hMon.handle) # or int(hMon)
temps = (
'UNKNOWN',
'4000K',
'5000K',
'6500K',
'7500K',
'8200K',
'9300K',
'10000K',
'11500K'
)
class MyEnum(c_int):
MC_COLOR_TEMPERATURE_UNKNOWN = 0
MC_COLOR_TEMPERATURE_4000K = 1
MC_COLOR_TEMPERATURE_5000K = 2
MC_COLOR_TEMPERATURE_6500K = 3
MC_COLOR_TEMPERATURE_7500K = 4
MC_COLOR_TEMPERATURE_8200K = 5
MC_COLOR_TEMPERATURE_9300K = 6
MC_COLOR_TEMPERATURE_10000K = 7
MC_COLOR_TEMPERATURE_11500K = 8
o = MyEnum()
print(o)
po = pointer(o)
pm = mons[0]
print("physical %r" % pm)
t = windll.dxva2.GetMonitorColorTemperature(pm, po) #byref(o))
if t:
#print(o)
#print(dir(po.contents))
print(temps[po.contents.value])
else:
print("Err: %s" % windll.kernel32.GetLastError()) # ERROR_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE = -1071241844 # Variable c_long '-0x03fd9da74'
Result:
<MyEnum object at 0x005D6120>
physical None
6500K
I'm trying to create a custom ComboBox that behaves like the one in here: http://chir.ag/projects/name-that-color/
I've got two problems right now:
I can't seem to find a way to have a scrollbar on the side; the gtk.rc_parse_string function should do that, since the ComboBox widget has a "appears-as-list" style property, but my custom widget seems unaffected for some reason.
When you select a color from my widget, then click the ComboBox again, instead of showing the selected item and its neighbours, the scrolled window starts from the top, for no apparent reason.
This is the code, you can pretty much ignore the __load_name_palette method. You need the /usr/share/X11/rgb.txt file to run this code, it looks like this: http://pastebin.com/raw.php?i=dkemmEdr
import gtk
import gobject
from os.path import exists
def window_delete_event(*args):
return False
def window_destroy(*args):
gtk.main_quit()
class ColorName(gtk.ComboBox):
colors = []
def __init__(self, name_palette_path, wrap_width=1):
gtk.ComboBox.__init__(self)
liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING)
name_palette = self.__load_name_palette(name_palette_path)
for c in name_palette:
r, g, b, name = c
if ((r + g + b) / 3.) < 128.:
fg = '#DDDDDD'
else:
fg = '#222222'
bg = "#%02X%02X%02X" % (r, g, b)
liststore.append((name, bg, fg))
self.set_model(liststore)
label = gtk.CellRendererText()
self.pack_start(label, True)
self.set_attributes(label, background=1, foreground=2, text=0)
self.set_wrap_width(wrap_width)
if len(name_palette) > 0:
self.set_active(0)
self.show_all()
def __load_name_palette(self, name_palette_path):
if exists(name_palette_path):
try:
f = open(name_palette_path,'r')
self.colors = []
palette = set()
for l in f:
foo = l.rstrip().split(None,3)
try:
rgb = [int(x) for x in foo[:3]]
name, = foo[3:]
except:
continue
k = ':'.join(foo[:3])
if k not in palette:
palette.add(k)
self.colors.append(rgb + [name])
f.close()
return self.colors
except IOError as (errno, strerror):
print "error: failed to open {0}: {1}".format(name_palette_path, strerror)
return []
else:
return []
if __name__ == '__main__':
win = gtk.Window()
#colname = ColorName('./ntc.txt')
colname = ColorName('/usr/share/X11/rgb.txt')
gtk.rc_parse_string("""style "mystyle" { GtkComboBox::appears-as-list = 1 }
class "GtkComboBox" style "mystyle" """)
print 'appears-as-list:', colname.style_get_property('appears-as-list')
model = gtk.ListStore(gobject.TYPE_STRING)
hbox = gtk.HBox()
win.add(hbox)
hbox.pack_start(colname)
win.connect('delete-event', window_delete_event)
win.connect('destroy', window_destroy)
win.show_all()
gtk.main()
The problem was the self.show_all() line. Also, you can't have a list AND a wrap_width != 1