2

I am creating a screenshot module using only pure python (ctypes), no big lib like win32, wx, QT, ... It has to manage multi-screens (what PIL and Pillow cannot).

Where I am blocking is when calling CreateDCFromHandle, ctypes.windll.gdi32 does not know this function. I looked at win32 source code to being inspired, but useless. As said in comment, this function does not exist in the MSDN, so what changes should I apply to take in consideration other screens?

This is the code which works for the primary monitor, but not for others: source code. It is blocking at the line 35. I tried a lot of combinations, looking for answers here and on others websites. But nothing functional for me ... It is just a screenshot!

Do you have clues?

Thanks in advance :)


Edit, I found my mystake! This is the code that works:

srcdc = ctypes.windll.user32.GetWindowDC(0)
memdc = ctypes.windll.gdi32.CreateCompatibleDC(srcdc)
bmp = ctypes.windll.gdi32.CreateCompatibleBitmap(srcdc, width, height)
ctypes.windll.gdi32.SelectObject(memdc, bmp)
ctypes.windll.gdi32.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)        
bmp_header = pack('LHHHH', calcsize('LHHHH'), width, height, 1, 24)
c_bmp_header = c_buffer(bmp_header) 
c_bits = c_buffer(' ' * (height * ((width * 3 + 3) & -4)))
got_bits = ctypes.windll.gdi32.GetDIBits(memdc, bmp, 0, height,
                        c_bits, c_bmp_header, DIB_RGB_COLORS)
# Here, got_bits should be equal to height to tell you all goes well.

French article with full explanations : Windows : capture d'écran

Tiger-222
  • 6,677
  • 3
  • 47
  • 60
  • MSDN doesn't seem to know that function. – icktoofay Jun 30 '13 at 20:52
  • That is right, after all searches I did, this function is always present into code source -- but not into the MSDN. Where can I apply changes to take in consideration other screens? – Tiger-222 Jun 30 '13 at 20:58
  • Hm no, I do not use argtypes. This code works on Windows 7 64 bits. Should I use argtypes if I do not declare functions? – Tiger-222 Jul 01 '13 at 22:01
  • Ah yes. It was python 32 bits on Windows 64 bits ... Just check'in. Thank you. – Tiger-222 Jul 01 '13 at 22:24
  • I tried and all works like a charm. Python 2.7, 3.0, 3.1, 3.2 and 3.3 in 64 bit version on Windows 7 64 bits => good. Could you tell me exactly what line you would add/change please? – Tiger-222 Jul 01 '13 at 23:07
  • The original post has a link to source code that is not valid. Ideally I would have left this as a comment but as I don't have enough reputation I'm going to leave this here: you can find the source code at https://github.com/BoboTiG/python-mss/blob/master/mss/windows.py – Peter May 13 '17 at 11:34

3 Answers3

2

Edit, I found my mystake! This is the code that works:

srcdc = ctypes.windll.user32.GetWindowDC(0)
memdc = ctypes.windll.gdi32.CreateCompatibleDC(srcdc)
bmp = ctypes.windll.gdi32.CreateCompatibleBitmap(srcdc, width, height)
ctypes.windll.gdi32.SelectObject(memdc, bmp)
ctypes.windll.gdi32.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)        
bmp_header = pack('LHHHH', calcsize('LHHHH'), width, height, 1, 24)
c_bmp_header = c_buffer(bmp_header) 
c_bits = c_buffer(' ' * (height * ((width * 3 + 3) & -4)))
got_bits = ctypes.windll.gdi32.GetDIBits(
    memdc, bmp, 0, height, c_bits, c_bmp_header, DIB_RGB_COLORS)
# Here, got_bits should be equal to height to tell you all goes well.
Tiger-222
  • 6,677
  • 3
  • 47
  • 60
1

Looking at the source for pywin32, CreateDCFromHandle is a fabrication. It does not exist in the Windows API; it is simply a bridge converting a Windows API thing into a pywin32 thing.

Since you're using ctypes rather than pywin32, no conversion is necessary; see if you can skip that step:

hwin = user.GetDesktopWindow()
hwindc = user.GetWindowDC(monitor['hmon'])
memdc = gdi.CreateCompatibleDC(hwindc)

When you're trying to do some native-Windows API thing with ctypes in Python, I find it more helpful to look at existing C code which already uses the Windows API rather than using Python code that uses a wrapper around it.

icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • Yes, I am visiting the MSDN since 5 days but the retranscription is not as good as wanted. I saw that this function is sensed give me a DC for all monitors: gdi32.CreateDCW('DISPLAY', 0, 0, 0) But the result is the same: only the primary monitor is grabbed. I will continue investigating. – Tiger-222 Jun 30 '13 at 21:08
  • @eryksun: Right, but that probably doesn't end up doing anything useful with the Windows API. It just wraps a Windows API handle in another object. – icktoofay Jun 30 '13 at 23:11
  • @eryksun: Yeah, I had already read that part of the code and saw that. I referenced it but didn't include it in my answer because I didn't think it was relevant. I will add a link to the source though, since that might be useful for anyone interested. – icktoofay Jun 30 '13 at 23:14
1

This isn't a Windows API function. You will need a combination of EnumDisplayDevices and CreateDC. Be aware that you must append "A" or "W" to the names of the functions depending on if you want to use ANSI strings or Unicode (widechar) strings.

Michael Butscher
  • 10,028
  • 4
  • 24
  • 25
  • Yes, EnumDisplayDevices() returns coordinates, windows handles and DC handles. But these values (handles) does not work, perhaps I misunderstand how it works. CreateDCW('DISPLAY', 0, 0, 0) should create a DC for all monitors (as said in the MSDN), but retrieving pixels from the 2nd monitor does not work. – Tiger-222 Jun 30 '13 at 21:26
  • @Tiger-222 Then you should try to retrieve the pixels for each monitor separately – Michael Butscher Jun 30 '13 at 22:04
  • It is what I want :) It is working for the primary monitor, but not for the 2nd. All seems to work until GetDIBits() which does not return the désired value. Black picture. – Tiger-222 Jun 30 '13 at 22:16