Author Topic: QB64 Hotkey Screen Capture  (Read 3743 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
QB64 Hotkey Screen Capture
« on: August 06, 2019, 05:58:17 am »
Code: [Select]
$SCREENHIDE

CONST MOD_SHIFT = 4

' these next two constants determine the hotkey.
' the current example is Shift+A
CONST fsModifiers = MOD_SHIFT '   http://msdn.microsoft.com/en-us/library/ms646309(v=vs.85).aspx
CONST vk = &H41 '                 http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx

CONST mode = 0

'mode 0 makes the thread wait for the hotkey. It will not respond to requests
'to terminate, until it gets a hotkey message. Otherwise, Windows will
'consider it to be not responding.
'mode 1 causes the thread to wake up at 1 second intervals. This makes it
'use some CPU time, but it won't appear to be not responding.

'As $SCREENHIDE is used, you won't have an X to click, so you would probably
'use task manager or process explorer to kill it either way. However, mode
'might make a difference in how much it delays your log off/shutdown time.

CONST WM_HOTKEY = &H0312
CONST PM_REMOVE = 1
CONST WAIT_FAILED = -1
CONST QS_ALLEVENTS = &H04BF

CONST ERROR_ALREADY_EXISTS = &HB7
DECLARE DYNAMIC LIBRARY "kernel32"
    FUNCTION GetLastError~& ()
    FUNCTION CreateMutexA~%& (BYVAL lpMutexAttributes~%&, BYVAL bInitialOwner&, BYVAL lpName~%&)
END DECLARE

DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION RegisterHotKey& (BYVAL hWnd~%&, BYVAL id&, BYVAL fsModifiers~&, BYVAL vk~&)
    FUNCTION UnregisterHotKey& (BYVAL hWnd~%&, BYVAL id&)
    FUNCTION GetMessageW& (BYVAL lpMsg~%&, BYVAL hWnd~%&, BYVAL wMsgFilterMin~&, BYVAL wMsgFilterMax~&)
    FUNCTION PeekMessageW& (BYVAL lpMsg~%&, BYVAL hWnd~%&, BYVAL wMsgFilterMin~&, BYVAL wMsgFilterMax~&, BYVAL wRemoveMsg~&)
    FUNCTION MsgWaitForMultipleObjects~& (BYVAL nCount~&, BYVAL pHandles~%&, BYVAL bWaitAll&, BYVAL dwMilliseconds~&, BYVAL dwWakeMask~&)
END DECLARE

TYPE POINT
    x AS LONG
    y AS LONG
END TYPE

TYPE MSG
    hwnd AS _UNSIGNED _OFFSET
    message AS _UNSIGNED LONG
    wParam AS _UNSIGNED _OFFSET
    lParam AS _OFFSET
    time AS _UNSIGNED LONG
    pt AS POINT
END TYPE


DIM h AS LONG
DIM bRet AS LONG
DIM msg AS MSG
DIM t AS STRING

t = "Global\qb64 hotkey demo" + CHR$(0)
IF 0 = CreateMutexA(0, 0, _OFFSET(t)) THEN showerr "CreateMutexA"
IF ERROR_ALREADY_EXISTS = GetLastError THEN showerr "(Multiple instances?) "

IF 0 = RegisterHotKey(0, 0, fsModifiers, vk) THEN showerr "RegisterHotKey"

IF mode THEN

    DO
        bRet = GetMessageW(_OFFSET(msg), 0, 0, 0)
        SELECT CASE bRet
            CASE 0: EXIT DO
            CASE -1: showerr "GetMessageW"
            CASE ELSE
                IF PeekMessageW(_OFFSET(msg), 0, 0, 0, PM_REMOVE) THEN
                    IF WM_HOTKEY = (&HFFFF~& AND msg.message) THEN
                        h = _SCREENIMAGE
                        SaveBMP "ScreenCapture" + timestamp + ".bmp", h, 0, 0, _WIDTH(h), _HEIGHT(h)
                        _FREEIMAGE h
                    END IF
                END IF
        END SELECT
    LOOP
ELSE
    DO
        _LIMIT 10
        'IF WAIT_FAILED = MsgWaitForMultipleObjects(0, 0, 0, 1000, QS_ALLEVENTS) THEN showerr "MsgWaitForMultipleObjects"
        IF PeekMessageW(_OFFSET(msg), 0, 0, 0, PM_REMOVE) THEN
            IF WM_HOTKEY = (&HFFFF~& AND msg.message) THEN
                h = _SCREENIMAGE
                SaveBMP "ScreenCapture" + timestamp + ".bmp", h, 0, 0, _WIDTH(h), _HEIGHT(h)
            END IF
        END IF
    LOOP

END IF

IF 0 = UnregisterHotKey(0, 0) THEN showerr "UnRegisterHotKey"
SYSTEM


SUB showerr (f AS STRING)
    _SCREENSHOW
    PRINT f; " failed. Error: 0x" + LCASE$(HEX$(GetLastError))
    END
END SUB


FUNCTION timestamp$
    DIM d AS STRING * 10
    DIM t AS STRING * 8
    DO
        d = DATE$
        t = TIME$
    LOOP WHILE d <> DATE$ ' try to prevent the situation where midnight is crossed between getting the date$ and time$
    MID$(t, 3, 1) = " "
    MID$(t, 6, 1) = " "
    MID$(d, 3, 1) = " "
    timestamp = RIGHT$(d, 4) + " " + LEFT$(d, 5) + "--" + t
END FUNCTION

SUB SaveBMP (filename$, image&, x1%, y1%, x2%, y2%)
    'Super special STEVE-Approved BMP Export routine for use with any QB64 graphic mode.
    IF x2% = _WIDTH(image&) THEN x2% = x2% - 1
    IF y2% = _HEIGHT(image&) THEN y2% = y2% - 1

    IF _PIXELSIZE(image&) = 0 THEN
        IF SaveTextAs256Color THEN
            tempimage& = TextScreenToImage256&(image&)
        ELSE
            tempimage& = TextScreenToImage32&(image&)
        END IF
        F = _FONT(image&)
        FW = _FONTWIDTH(F): FH = _FONTHEIGHT(F)
        SaveBMP filename$, tempimage&, x1% * FW, y1% * FH, x2% * FW, y2% * FH
        _FREEIMAGE tempimage&
        EXIT FUNCTION
    END IF

    TYPE BMPFormat
        ID AS STRING * 2
        Size AS LONG
        Blank AS LONG
        Offset AS LONG
        Hsize AS LONG
        PWidth AS LONG
        PDepth AS LONG
        Planes AS INTEGER
        BPP AS INTEGER
        Compression AS LONG
        ImageBytes AS LONG
        Xres AS LONG
        Yres AS LONG
        NumColors AS LONG
        SigColors AS LONG
    END TYPE


    DIM BMP AS BMPFormat
    DIM x AS LONG, y AS LONG
    DIM temp AS STRING, t AS STRING * 1

    DIM n AS _MEM, o AS _OFFSET, m AS _MEM
    m = _MEMIMAGE(image&)

    IF x1% > x2% THEN SWAP x1%, x2%
    IF y1% > y2% THEN SWAP y1%, y2%
    IF x2% = _WIDTH(imagehandle%) THEN x2% = _WIDTH(imagehandle%) - 1 'troubleshoot in case user does a common mistake for 0-width instead of 0 - (width-1) for fullscreen
    IF y2% = _HEIGHT(imagehandle%) THEN y2% = _HEIGHT(imagehandle%) - 1 'troubleshoot in case user does a common mistake for 0-width instead of 0 - (width-1) for fullscreen

    s& = _SOURCE
    _SOURCE image&

    BMP.PWidth = (x2% - x1%) + 1
    BMP.PDepth = (y2% - y1%) + 1
    BMP.ID = "BM"
    BMP.Blank = 0
    BMP.Hsize = 40
    BMP.Planes = 1
    BMP.Compression = 0
    BMP.Xres = 0
    BMP.Yres = 0

    BMP.SigColors = 0

    SELECT CASE _PIXELSIZE(image&)
        CASE 1
            temp = SPACE$(x2% - x1% + 1)
            OffsetBITS& = 54 + 1024 'add palette in 256 color modes
            BMP.BPP = 8
            IF BMP.PWidth MOD 4 THEN ZeroPAD$ = SPACE$(4 - (BMP.PWidth MOD 4))
            ImageSize& = (BMP.PWidth + LEN(ZeroPAD$)) * BMP.PDepth
            BMP.ImageBytes = ImageSize&
            BMP.NumColors = 256
            BMP.Size = ImageSize& + OffsetBITS&
            BMP.Offset = OffsetBITS&
        CASE 4
            temp = SPACE$(3)
            OffsetBITS& = 54 'no palette in 24/32 bit
            BMP.BPP = 24
            IF ((BMP.PWidth * 3) MOD 4) THEN ZeroPAD$ = SPACE$(4 - ((BMP.PWidth * 3) MOD 4))
            ImageSize& = (BMP.PWidth + LEN(ZeroPAD$)) * BMP.PDepth
            BMP.ImageBytes = ImageSize&
            BMP.NumColors = 0
            BMP.Size = ImageSize& * 3 + OffsetBITS&
            BMP.Offset = OffsetBITS&
    END SELECT

    F = FREEFILE
    n = _MEMNEW(BMP.Size)
    _MEMPUT n, n.OFFSET, BMP
    o = n.OFFSET + 54
    zp& = LEN(ZeroPAD$)
    $CHECKING:OFF

    IF BMP.BPP = 8 THEN 'Store the Palette for 256 color mode
        FOR c& = 0 TO 255 ' read BGR color settings from JPG image + 1 byte spacer(CHR$(0))
            cv& = _PALETTECOLOR(c&, image) ' color attribute to read.
            b$ = CHR$(_BLUE32(cv&)) + CHR$(_GREEN32(cv&)) + CHR$(_RED32(cv&)) + CHR$(0) 'spacer byte
            _MEMPUT n, o, b$
            o = o + 4
        NEXT
        y = y2% + 1
        w& = _WIDTH(image&)
        x = x2% - x1% + 1
        DO
            y = y - 1
            _MEMGET m, m.OFFSET + (w& * y + x1%), temp
            _MEMPUT n, o, temp
            o = o + x
            _MEMPUT n, o, ZeroPAD$
            o = o + zp&
        LOOP UNTIL y = y1%
    ELSE
        y = y2% + 1
        w& = _WIDTH(image&)
        DO
            y = y - 1: x = x1% - 1
            DO
                x = x + 1
                _MEMGET m, m.OFFSET + (w& * y + x) * 4, temp
                _MEMPUT n, o, temp
                o = o + 3
            LOOP UNTIL x = x2%
            _MEMPUT n, o, ZeroPAD$
            o = o + zp&
        LOOP UNTIL y = y1%
    END IF
    $CHECKING:ON
    _MEMFREE m
    OPEN filename$ FOR BINARY AS #F
    t1$ = SPACE$(BMP.Size)
    _MEMGET n, n.OFFSET, t1$
    PUT #F, , t1$
    _MEMFREE n
    CLOSE #F
    _SOURCE s&
END SUB

A simple program to showcase how we can set a hotkey (in this case SHIFT-A) and have a program run in the background while hidden.  NOTE, you'll need to use explorer to terminate this demo, as the screen hides itself and becomes invisible for us.

All this does is set Shift-A as a hotkey combination so that anytime we press it, we end up grabbing and saving a screen shot of our desktop.

Note 2: Windows-Only code, as this relies on windows libraries to work properly.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!