Sideband/sbapp/plyer/platforms/win/screenshot.py

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 sbapp.plyer.facades import Screenshot
from sbapp.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()