108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
|
'''
|
||
|
https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-bitblt
|
||
|
https://www.bugs.python.org/issue33656
|
||
|
'''
|
||
|
|
||
|
from os import getpid
|
||
|
from os.path import join
|
||
|
from ctypes import windll, c_int, addressof
|
||
|
|
||
|
from win32gui import GetDesktopWindow, GetWindowDC
|
||
|
from win32api import GetSystemMetrics
|
||
|
from win32ui import CreateDCFromHandle, CreateBitmap
|
||
|
from win32con import (
|
||
|
SM_CXVIRTUALSCREEN,
|
||
|
SM_CYVIRTUALSCREEN,
|
||
|
SM_XVIRTUALSCREEN,
|
||
|
SM_YVIRTUALSCREEN,
|
||
|
SRCCOPY
|
||
|
)
|
||
|
|
||
|
from plyer.facades import Screenshot
|
||
|
from plyer.platforms.win.storagepath import WinStoragePath
|
||
|
|
||
|
|
||
|
class WinScreenshot(Screenshot):
|
||
|
def __init__(self, file_path=None):
|
||
|
default_path = join(
|
||
|
WinStoragePath().get_pictures_dir(), 'screenshot.bmp'
|
||
|
)
|
||
|
super().__init__(file_path or default_path)
|
||
|
|
||
|
def _set_dpi_aware(self, value):
|
||
|
try:
|
||
|
windll.shcore.SetProcessDpiAwareness(value)
|
||
|
except (AttributeError, OSError):
|
||
|
print('Could not set DPI awareness.')
|
||
|
|
||
|
def _dpi_aware(self):
|
||
|
# make backup of DPI awareness value in case a user does not want
|
||
|
# to use it, otherwise we'll cripple user's runtime
|
||
|
try:
|
||
|
process_handle = windll.kernel32.OpenProcess(
|
||
|
0, # no permissions
|
||
|
False, # bInheritHandle
|
||
|
getpid()
|
||
|
)
|
||
|
aware = c_int()
|
||
|
windll.shcore.GetProcessDpiAwareness(
|
||
|
process_handle,
|
||
|
addressof(aware)
|
||
|
)
|
||
|
finally:
|
||
|
# always close the handle!
|
||
|
windll.kernel32.CloseHandle(process_handle)
|
||
|
return bool(aware)
|
||
|
|
||
|
def _capture(self):
|
||
|
# make sure the process is DPI aware, otherwise the image
|
||
|
# is only a part of the full monitor content
|
||
|
# (necessary only on Win 8.1+)
|
||
|
old_awareness = self._dpi_aware()
|
||
|
self._set_dpi_aware(True)
|
||
|
|
||
|
# get width and height of current monitor
|
||
|
width = GetSystemMetrics(SM_CXVIRTUALSCREEN)
|
||
|
height = GetSystemMetrics(SM_CYVIRTUALSCREEN)
|
||
|
|
||
|
# get 'desktop' window handle
|
||
|
handle_desktop = GetDesktopWindow()
|
||
|
|
||
|
# get window graphic context handle
|
||
|
handle_context = GetWindowDC(handle_desktop)
|
||
|
|
||
|
# create device context
|
||
|
dev_ctx = CreateDCFromHandle(handle_context)
|
||
|
|
||
|
# create destination for original device context
|
||
|
dest_ctx = dev_ctx.CreateCompatibleDC()
|
||
|
|
||
|
# create bitmap compatible with desktop window device
|
||
|
bmp = CreateBitmap()
|
||
|
bmp.CreateCompatibleBitmap(dev_ctx, width, height)
|
||
|
|
||
|
# select bitmap into destination device
|
||
|
dest_ctx.SelectObject(bmp)
|
||
|
|
||
|
# populate selected bitmap in destination device
|
||
|
dest_ctx.BitBlt(
|
||
|
(0, 0), # start point (x, y)
|
||
|
(width, height), # size of rectangle
|
||
|
dev_ctx, # source device
|
||
|
(
|
||
|
GetSystemMetrics(SM_XVIRTUALSCREEN),
|
||
|
GetSystemMetrics(SM_YVIRTUALSCREEN)
|
||
|
), # source rectangle, can be different monitor
|
||
|
SRCCOPY # copy directly without filters
|
||
|
)
|
||
|
|
||
|
# save bitmap to file
|
||
|
bmp.SaveBitmapFile(dest_ctx, self.file_path)
|
||
|
|
||
|
# return to the original state
|
||
|
self._set_dpi_aware(old_awareness)
|
||
|
|
||
|
|
||
|
def instance():
|
||
|
return WinScreenshot()
|