Jump to content

Avoid click-through in 100% transparent $WS_EX_LAYERED window


Go to solution Solved by ioa747,

Recommended Posts

Hello fellow friends,

I have a working code for a simple mouse selection rectangle on a transparent window.
Works fine for the first click, hold, drag, and release. A green rectangle is drawn.
But at the second attempt of dragging the rectangle the GUI suddenly becomes click-through and the code/text in the underlying Skite IDE gets selected while dragging.
I guess it is because something changes after the _WinAPI_UpdateLayeredWindow() command.
Not sure what exactly it is...

 

#include <Misc.au3>
#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>

; DPI awareness Win10 & Win11
If @OSVersion = 'WIN_10' Or @OSVersion = 'WIN_11' Then
    DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "hwnd", -2)
EndIf

HotKeySet("{ESC}", "End")

_GDIPlus_Startup()

Local $hGUI = GUICreate("", @DesktopWidth, @DesktopHeight, 0, 0, $WS_POPUP, BitOR($WS_EX_TOPMOST, $WS_EX_LAYERED))
Local $hGDC = _WinAPI_GetDC($hGUI)
Local $hMDC = _WinAPI_CreateCompatibleDC($hGDC)
Local $hBit = _WinAPI_CreateCompatibleBitmap($hGDC, @DesktopWidth, @DesktopHeight)
Local $hOld = _WinAPI_SelectObject($hMDC, $hBit)
Local $hGFX = _GDIPlus_GraphicsCreateFromHDC($hMDC)
Local $hPen = _GDIPlus_PenCreate(0xFF00FF00, 2)

_GDIPlus_GraphicsSetSmoothingMode($hGFX, $GDIP_SMOOTHINGMODE_HIGHQUALITY)
GUISetState(@SW_SHOW, $hGUI)

While 1
    StartMouseTracking($hGUI, $hGDC, $hMDC, $hGFX, $hPen)
WEnd

Func StartMouseTracking($hGUI, $hGDC, $hMDC, $hGFX, $hPen)
    Local $topLeft[2], $bottomRight[2]
    Local $isDragging = False

    While 1
        If _IsPressed("01") Then ; Left mouse button is pressed down
            If Not $isDragging Then
                $topLeft[0] = MouseGetPos(0)
                $topLeft[1] = MouseGetPos(1)
                $isDragging = True
            Else
                ; Mouse dragging
                Local $currentX = MouseGetPos(0)
                Local $currentY = MouseGetPos(1)
                If $currentX <> $bottomRight[0] Or $currentY <> $bottomRight[1] Then
                    $bottomRight[0] = $currentX
                    $bottomRight[1] = $currentY
                    DrawRectangle($topLeft[0], $topLeft[1], $bottomRight[0], $bottomRight[1], $hGUI, $hGDC, $hMDC, $hGFX, $hPen)
                EndIf
            EndIf
        ElseIf $isDragging Then
            ; Left mouse button is released
            $isDragging = False
            ConsoleWrite($topLeft[0] & " " & $topLeft[1] & " " & $bottomRight[0] & " " & $bottomRight[1] & @LF)
            ExitLoop
        EndIf
    WEnd
EndFunc

Func DrawRectangle($x1, $y1, $x2, $y2, $hGUI, $hGDC, $hMDC, $hGFX, $hPen)
    _GDIPlus_GraphicsClear($hGFX, 0x00000000) ; Clear with transparent background
    _GDIPlus_GraphicsDrawRect($hGFX, $x1, $y1, $x2 - $x1, $y2 - $y1, $hPen)

    Local $tSize = DllStructCreate("int;int")
    Local $tPointSource = DllStructCreate("int;int")
    Local $tPointDest = DllStructCreate("int;int")
    Local $tBlend = DllStructCreate("byte;byte;byte;byte")

    DllStructSetData($tSize, 1, @DesktopWidth)
    DllStructSetData($tSize, 2, @DesktopHeight)
    DllStructSetData($tBlend, 1, 0) ; AC_SRC_OVER
    DllStructSetData($tBlend, 2, 0) ; 0
    DllStructSetData($tBlend, 3, 255) ; Alpha value 0-255
    DllStructSetData($tBlend, 4, 1) ; AC_SRC_ALPHA | Has Alpha = 1 | Has No Alpha = 0

    _WinAPI_UpdateLayeredWindow($hGUI, $hGDC, $tPointDest, $tSize, $hMDC, $tPointSource, 0, $tBlend, $ULW_ALPHA)

EndFunc

Func End()
    _GDIPlus_PenDispose($hPen) ; Cleanup pen
    _GDIPlus_GraphicsDispose($hGFX) ; Cleanup graphics object
    _WinAPI_ReleaseDC($hGUI, $hGDC) ; Release device context
    _WinAPI_SelectObject($hMDC, $hOld) ; Restore the old object
    _WinAPI_DeleteObject($hBit) ; Delete the bitmap
    _WinAPI_DeleteDC($hMDC) ; Delete the memory device context
    _GDIPlus_Shutdown() ; Shutdown GDI+
    GUIDelete($hGUI) ; Delete overlay window
    Exit
EndFunc


Thanks!
 

Link to comment
Share on other sites

  • Solution
Posted (edited)

Here, in a primitive way, I placed a block window

; make mouse block gui
Local $block_gui = GUICreate("block_gui", 100, 100, -1, -1, $WS_POPUP, BitOR($WS_EX_TOOLWINDOW, $WS_EX_TOPMOST))
WinSetTrans($block_gui, "", 1)
GUISetState(@SW_SHOW, $block_gui)
GUISetCursor($MCID_CROSS, 1, $block_gui)

Edit:
here , more UpDate version

;move the $block_gui to active monitor
If $sCurDevice <> $sDevice Then
    $sCurDevice = $sDevice
    ;ConsoleWrite("- $sCurDevice=" & $sCurDevice & @CRLF)
    WinMove($block_gui, "", $iDeskLeft, $iDeskTop, $iDeskWidth, $iDeskHeight)
EndIf

 

Edited by ioa747

I know that I know nothing

Link to comment
Share on other sites

@ioa747 Hi and thanks for your reply!

Before I started this thread I was also experimenting with:

$hGUI = GUICreate("", 100, 100, -1, -1, $WS_POPUP, BitOR($WS_EX_TOOLWINDOW, $WS_EX_TOPMOST))
WinSetTrans($hGUI, "", 1)

In "WinSetTrans()" I found that if you use 0 then it becomes click-through. But everything from 1-255 becomes non click-through.
Also, I was unable to draw opaque GFX elements on that "$WS_EX_TOOLWINDOW" because they get the same transparency as the window.

 

So when I look at the code of your "NewCapture()" function.
Do you suggest that I should use 2 transparent windows?
One to Draw on and one to block the mouse from the background?

Thank you!

Link to comment
Share on other sites

taking a second look

below the line:
_WinAPI_UpdateLayeredWindow($hGUI, $hGDC, $tPointDest, $tSize, $hMDC, $tPointSource, 0, $tBlend, $ULW_ALPHA)


Add:
WinActivate($hGUI)

I know that I know nothing

Link to comment
Share on other sites

I just noticed 2 things.
1. I put the solution on my post instead of  @ioa747 and i don't know how to change that.
2. The solution is not really a 100% solution because the first mouse click still strikes through although the dragging of the mouse does not.

So... Im still working on it.

Link to comment
Share on other sites

Okay, here is the final solution.

Two windows.
One window to draw on.
Another window to block the mouse.
100% blocking mouse interactions with any underlying window.

 

#include <GDIPlus.au3>
#include <WindowsConstants.au3>

HotKeySet("{ESC}", "End")

; DPI awareness Win10 & Win11
If @OSVersion = 'WIN_10' Or @OSVersion = 'WIN_11' Then
    DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "hwnd", -2)
EndIf

_GDIPlus_Startup()

Local $hGUI = GUICreate("", @DesktopWidth, @DesktopHeight, 0, 0, $WS_POPUP, BitOR($WS_EX_TOPMOST, $WS_EX_LAYERED)) ;        Main GUI
Local $hBLK = GUICreate("", @DesktopWidth, @DesktopHeight, 0, 0, $WS_POPUP, BitOR($WS_EX_TOPMOST, $WS_EX_TOOLWINDOW)) ;     Mouse Block GUI

WinSetTrans($hBLK, "", 1) ; 0 = clicktrough | > 0 = Non clickthrough

Local $hGDC = _WinAPI_GetDC($hGUI)
Local $hMDC = _WinAPI_CreateCompatibleDC($hGDC)
Local $hBit = _WinAPI_CreateCompatibleBitmap($hGDC, @DesktopWidth, @DesktopHeight)
Local $hOld = _WinAPI_SelectObject($hMDC, $hBit)
Local $hGFX = _GDIPlus_GraphicsCreateFromHDC($hMDC)
Local $hPen = _GDIPlus_PenCreate(0xFF00FF00, 2)

_GDIPlus_GraphicsSetSmoothingMode($hGFX, $GDIP_SMOOTHINGMODE_HIGHQUALITY)
GUISetState(@SW_SHOW, $hBLK)
GUISetState(@SW_SHOW, $hGUI)

; Pre-create structs outside the function if they do not change
Global $tSize = DllStructCreate("int;int")
Global $tPSrc = DllStructCreate("int;int")
Global $tPDst = DllStructCreate("int;int")
Global $tBlnd = DllStructCreate("byte;byte;byte;byte")

; Initialize constant values in the structs
DllStructSetData($tSize, 1, @DesktopWidth)
DllStructSetData($tSize, 2, @DesktopHeight)
DllStructSetData($tBlnd, 3, 255) ; Alpha value 0-255
DllStructSetData($tBlnd, 4, 1)   ; AC_SRC_ALPHA | Has Alpha = 1 | Has No Alpha = 0

While 1
    StartMouseTracking($hGUI, $hGDC, $hMDC, $hGFX, $hPen)
WEnd

Func StartMouseTracking($hGUI, $hGDC, $hMDC, $hGFX, $hPen)
    Local $topLeft[2], $bottomRight[2], $aCall
    Local $isDragging = False
    While 1
        $aCall = DllCall("user32.dll", "short", "GetAsyncKeyState", "int", 0x01)
        If BitAND($aCall[0], 0x8000) <> 0 Then ; Left mouse button is pressed down
            If Not $isDragging Then
                $topLeft[0] = MouseGetPos(0)
                $topLeft[1] = MouseGetPos(1)
                $isDragging = True
            Else ; Mouse is dragging
                Local $currentX = MouseGetPos(0)
                Local $currentY = MouseGetPos(1)
                If $currentX <> $bottomRight[0] Or $currentY <> $bottomRight[1] Then
                    $bottomRight[0] = $currentX
                    $bottomRight[1] = $currentY
                    DrawRectangle($topLeft[0], $topLeft[1], $bottomRight[0], $bottomRight[1], $hGUI, $hGDC, $hMDC, $hGFX, $hPen)
                EndIf
            EndIf
        ElseIf $isDragging Then ; Left mouse button is released
            $isDragging = False
            ConsoleWrite($topLeft[0] & " " & $topLeft[1] & " " & $bottomRight[0] & " " & $bottomRight[1] & @LF)
            ExitLoop
        EndIf
    WEnd
EndFunc   ;==>StartMouseTracking

Func DrawRectangle(ByRef $x1, ByRef $y1, ByRef $x2, ByRef $y2, ByRef $hGUI, ByRef $hGDC, ByRef $hMDC, ByRef $hGFX, ByRef $hPen)
    _GDIPlus_GraphicsClear($hGFX, 0x00000000) ; Clear with transparent background
    _GDIPlus_GraphicsDrawRect($hGFX, $x1, $y1, $x2 - $x1, $y2 - $y1, $hPen)
    DllCall("user32.dll", "bool", "UpdateLayeredWindow", "hwnd", $hGUI, "handle", $hGDC, "struct*", $tPDst, "struct*", $tSize, "handle", $hMDC, "struct*", $tPSrc, "dword", 0, "struct*", $tBlnd, "dword", 0x02) ; FAST _WinAPI_UpdateLayeredWindow $ULW_ALPHA = 0x02
EndFunc   ;==>DrawRectangle

Func End()
    _GDIPlus_PenDispose($hPen) ; Cleanup pen
    _GDIPlus_GraphicsDispose($hGFX) ; Cleanup graphics object
    _WinAPI_ReleaseDC($hGUI, $hGDC) ; Release device context
    _WinAPI_SelectObject($hMDC, $hOld) ; Restore the old object
    _WinAPI_DeleteObject($hBit) ; Delete the bitmap
    _WinAPI_DeleteDC($hMDC) ; Delete the memory device context
    _GDIPlus_Shutdown() ; Shutdown GDI+
    GUIDelete($hGUI) ; Delete overlay window
    Exit
EndFunc   ;==>End

 

@ioa747 Thanks man for the inspiration!

 

Link to comment
Share on other sites

You need a sleep(10) before the WEnd in the tracking function at line 69, it's eating a whole core as it is now.

Maybe also make it so the mouse can be moved in any direction, not just from left to right and from top to bottom, but also from right to left and bottom to top.

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

On 6/25/2024 at 12:58 PM, Blaxxun said:

@Werty Yes, you are right. I will add even more. Like: Move whole rectangle after selection and adjusting each side of the rectangle.
But until now my focus was to get rid of the click-through problem.

Hi @Blaxxun... if you want to try something similar you can take a look at my _crop() function found here:

https://www.autoitscript.com/forum/topic/203545-%E2%9C%82%EF%B8%8F-quick-crop-tool

Below is a simple script if you're interested in trying it out. 
I hope it can be of use to you.

#include <GUIConstantsEx.au3>
#include <croptool.au3> ; <-- https://www.autoitscript.com/forum/topic/203545-%E2%9C%82%EF%B8%8F-quick-crop-tool
Opt("GUICloseOnESC", 0)

; Simple example of use of the _Crop() function
;
; it allows you to freely and visually select an area of the screen
;
;  - To resize the area click the left mouse button on any of the moving colored edges of the tool and drag.
;    You can also LeftClick within the selected area and drag to move the whole selector.
;
;  - To terminate the selection operation RightClick within the selected area.
;
;    You can alse terminate the selection operation by hitting the ESC key;
;    in that case the @extended macro will be setted to true

Example()

Func Example()
    ; Create an example GUI
    Local $hGui = GUICreate("Crop Demo", 220, 105, -1, -1, -1, BitOR($WS_EX_TOOLWINDOW, $WS_EX_TOPMOST))

    ; Create buttons
    Local $Crop = GUICtrlCreateButton("Start cropping", 10, 10, 200, 25)
    Local $Crop_hide_me = GUICtrlCreateButton("Hide me and crop", 10, 40, 200, 25)
    Local $Close = GUICtrlCreateButton("Close", 10, 70, 200, 25)

    ; Display the example GUI
    GUISetState(@SW_SHOW, $hGui)

    Local $aSelection = 0, $bExtended = 0

    ; Loop until the user exits.
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE, $Close
                ExitLoop

            Case $Crop
                ; start the crop tool
                $aSelection = _crop()
                $bExtended = @extended

            Case $Crop_hide_me
                ; hide this gui and start the crop tool
                GUISetState(@SW_HIDE, $hGui)
                $aSelection = _crop()
                $bExtended = @extended
                GUISetState(@SW_SHOW, $hGui)

        EndSwitch

        If $bExtended Then
            MsgBox(48, "Esc pressed", "no crop area selected")
            $aSelection = ''
            $bExtended = 0
        EndIf

        If IsArray($aSelection) Then

            MsgBox(64, 'Coordinates of the selected area', _
                    "UpperLeft X    : " & $aSelection[0] & @CRLF & _
                    "UpperLeft Y    : " & $aSelection[1] & @CRLF & _
                    "Width      : " & $aSelection[2] & @CRLF & _
                    "Height     : " & $aSelection[3])

            $aSelection = ''

        EndIf
    WEnd

    ; Delete the GUI
    GUIDelete($hGui)

EndFunc   ;==>Example

 

Edited by Gianni

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...