Jump to content

Recommended Posts

Posted

I'm trying to make a script that will add a secondary icon to the taskbar when I have unread emails. Outlook doesn't show this well and I sometimes miss emails for an hour or more.

Currently, the script creates a GUI, draws a red circle with a black border, then draws some text on top of that. The number will eventually be the unread email count, but for testing it's just an incrementing number. That all works fine. The issue seems to be when I'm trying to convert the GDIPlus Graphic into a Icon to display in the taskbar. Instead of drawing the same circle with text over Outlook's icon, it draws a solid black square. According to MS's documentation, I need to pass a HICON (a handle to an icon?) to the function ITaskbarList3::SetOverlayIcon. I've tried a few different things, but here's the current code:

#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WinAPIIcons.au3>
; #include <Taskbar.au3> ; A UDF I created for myself. Instead I extracted the necessary things to this file

Global Const $__g_sCLSID_TaskbarList = "{56FDF344-FD6D-11D0-958A-006097C9A090}"
Global Const $__g_sIID_ITaskbarList3 = "{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"
Global Const $__g_tagITaskbarList3 = _
    "HrInit hresult();" & _
    "AddTab hresult(hwnd);" & _
    "DeleteTab hresult(hwnd);" & _
    "ActivateTab hresult(hwnd);" & _
    "SetActiveAlt hresult(hwnd);" & _
    "MarkFullscreenWindow hresult(hwnd;boolean);" & _
    "SetProgressValue hresult(hwnd;uint64;uint64);" & _
    "SetProgressState hresult(hwnd;int);" & _
    "RegisterTab hresult(hwnd;hwnd);" & _
    "UnregisterTab hresult(hwnd);" & _
    "SetTabOrder hresult(hwnd;hwnd);" & _
    "SetTabActive hresult(hwnd;hwnd;dword);" & _
    "ThumbBarAddButtons hresult(hwnd;uint;ptr);" & _
    "ThumbBarUpdateButtons hresult(hwnd;uint;ptr);" & _
    "ThumbBarSetImageList hresult(hwnd;ptr);" & _
    "SetOverlayIcon hresult(hwnd;ptr;wstr);" & _
    "SetThumbnailTooltip hresult(hwnd;wstr);" & _
    "SetThumbnailClip hresult(hwnd;ptr);"

Global $__g_iFontSize = 12
Main()

Func Main()
    ; Create GUI
    Local $hGUI = GUICreate("GDI+", 400, 300)
    GUISetState(@SW_SHOW)

    Local $iX = 138, $iY = 108, $iWidth = 32, $iHeight = 32, $iBorder = 1

    _GDIPlus_Startup()
    Local $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    Local $hTextBrush = _GDIPlus_BrushCreateSolid(0xFFFFFFFF)
    Local $hFormat = _GDIPlus_StringFormatCreate()
    Local $hFamily = _GDIPlus_FontFamilyCreate("Arial")
    Local $hFont = _GDIPlus_FontCreate($hFamily, $__g_iFontSize)
    Local $tLayout = _GDIPlus_RectFCreate($iX + 2, $iY + 2, $iWidth, $iHeight - 5)
    Local $hBackgroundBrush = _GDIPlus_BrushCreateSolid(0xFFFF0000)
    Local $hPen = _GDIPlus_PenCreate(0xFF000000, $iBorder)
    Local $iIndex = 0
    Local $hTimer = TimerInit()
    Local $oTB = _Taskbar_Init()
    Local $hBitmap, $hIcon
    ; Loop until the user exits.
    Do
        ; only update after 1 second
        If TimerDiff($hTimer) > 1000 Then
            If $iIndex > 0 Then
                _WinAPI_DestroyIcon($hIcon)
                _GDIPlus_BitmapDispose($hBitmap)
            EndIf
            ; Clear the graphics
            _GDIPlus_GraphicsClear($hGraphic, 0xFFFFFFFF)
            ; Draw a filled circle
            _GDIPlus_GraphicsFillEllipse($hGraphic, $iX, $iY, $iWidth, $iHeight, $hBackgroundBrush)
            ; Draw a border circle
            _GDIPlus_GraphicsDrawEllipse($hGraphic, $iX, $iY, $iWidth, $iHeight, $hPen)
            ; Draw the text in the circle
            DrawStringCentered($hGraphic, $iIndex, $hFont, $tLayout, $hFormat, $hTextBrush)
            ; Extract the graphic to a bitmap
            $hBitmap = _GDIPlus_BitmapCreateFromGraphics($iWidth, $iHeight, $hGraphic)
            If ErrMsg(_GDIPlus_BitmapCreateFromGraphics) Then Exit
            ; Convert the Bitmap into an icon
            $hIcon = _GDIPlus_HICONCreateFromBitmap($hBitmap)
            If ErrMsg(_GDIPlus_HICONCreateFromBitmap) Then Exit

            $hIcon = _WinAPI_Create32BitHICON($hIcon, true)
            If ErrMsg(_WinAPI_Create32BitHICON) Then Exit

            ; Add the icon to the window
            AddIconToWin("Inbox", $oTB, $hIcon)
            $iIndex += 1
            if($iIndex > 99) Then $iIndex = 99
            $hTimer = TimerInit()
        EndIf
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    ; Clean up resources
    _GDIPlus_FontDispose($hFont)
    _GDIPlus_PenDispose($hPen)
    _GDIPlus_FontFamilyDispose($hFamily)
    _GDIPlus_StringFormatDispose($hFormat)
    _GDIPlus_BrushDispose($hTextBrush)
    _GDIPlus_GraphicsDispose($hGraphic)
    _GDIPlus_Shutdown()

    ; Clear the icon
    AddIconToWin("Inbox", $oTB, null)
EndFunc

Func DrawStringCentered($hGraphic, $iIndex, $hFont, $tLayout, $hFormat, $hTextBrush)
    
    ; Determine the width of the string
    Local $aInfo = _GDIPlus_GraphicsMeasureString($hGraphic, $iIndex, $hFont, $tLayout, $hFormat)
    Local $tIndex = $aInfo[0]
    ; Offset + Half of (Area's width - font's width)
    $tIndex.X = $tLayout.X + (($tLayout.Width - $tIndex.Width) / 2)
    ; Offset + Half of (Area's width - font's width)
    $tIndex.Y = $tLayout.Y + (($tLayout.Height - _GDIPlus_FontGetHeight($hFont, $hGraphic)) / 2)
    ; Finally, draw the string
    _GDIPlus_GraphicsDrawStringEx($hGraphic, $iIndex, $hFont, $tIndex, $hFormat, $hTextBrush)

EndFunc

Func AddIconToWin($sSearch, $oTB, $hIcon)
    
    Local $hWnd = WinGetHandle($sSearch)
    if ErrMsg(WinGetHandle) Then Exit
    
    _Taskbar_SetOverlayIcon($oTB, $hWnd, $hIcon, "Custom Icon")
    If ErrMsg(_Taskbar_SetOverlayIcon) Then Exit

EndFunc

Func AddIconToOutlook()
    Local $hWnd = WinGetHandle("Inbox")
    if ErrMsg(WinGetHandle) Then Exit
    Debug(WinGetTitle($hWnd))

    Local $oTB = _Taskbar_Init()
    If ErrMsg(_Taskbar_Init) Then Exit

    Local $hIcon = _WinAPI_ShellExtractIcon(@SystemDir & "\shell32.dll", 109, 64, 64)
    If ErrMsg(_WinAPI_ShellExtractIcon) Then Exit

    _Taskbar_SetOverlayIcon($oTB, $hWnd, $hIcon, "No Entry")
    If ErrMsg(_Taskbar_SetOverlayIcon) Then Exit

    Debug("Should've set the icon now? I think?")
EndFunc

Func Debug($sMsg, $sPrefix = "+")
    ConsoleWrite($sPrefix & $sMsg & @CRLF)
EndFunc

Func ErrMsg($sMsg = "", $iError = @error, $iExtended = @extended, $iScriptLineNum = @ScriptLineNumber)
    Local $__g_bUsingVSCode = true
    ; If there is an error, then write the error message
    If $iError Then
        ; If we've used a Function as the message (it looks really nice when I do) then print the name of the function
        If IsFunc($sMsg) Then $sMsg = FuncName($sMsg)
        Local $sText = ""
        If StringInStr(@ScriptName, ".au3") Then 
            If $__g_bUsingVSCode Then
                $sText = @ScriptFullPath & ":" & $iScriptLineNum & ":5 - See below for message" & @CRLF
            Else
                $sText = '"' & @ScriptFullPath & '" (' & $iScriptLineNum & ',5) : (See below for message)' & @CRLF
            EndIf
        EndIf
        If $iExtended <> 0 Then $sMsg = "Extended: " & $iExtended & " - " & $sMsg
        $sText &= "! Error: " & $iError & " - " & $sMsg
        Debug($sText, "")
    EndIf
    ; Preserve the error
    Return SetError($iError, $iExtended, $iError)

EndFunc


Func _Taskbar_Init()
    
    Local $oTB = ObjCreateInterface($__g_sCLSID_TaskbarList, $__g_sIID_ITaskbarList3, $__g_tagITaskbarList3)
    Local $oErrHnd = ObjEvent("AutoIt.Error", FuncName(__Taskbar_ErrorHandler))
    $oTB.HrInit()
    If @error Then Return SetError(@error, @extended, False)
    Return $oTB

EndFunc   ;==>_Taskbar_Init

; These are icons added to your taskbar icon, showing status usually. Each window only gets 1. Set $hIcon to NULL to clear. 
; (Win11) Teams uses this to show your status (busy, free, inactive) and Outlook uses it to show if you have an unread email
; Returns True on success, sets @error on failure
Func _Taskbar_SetOverlayIcon(ByRef $oTB, $hWnd, $hIcon, $sAltText = "")
    
    Local $oErrHnd = ObjEvent("AutoIt.Error", FuncName(__Taskbar_ErrorHandler))
    Local $vRet = $oTB.SetOverlayIcon($hWnd, $hIcon, $sAltText)
    If @error Then Return SetError(@error, @extended, False)
    If $vRet = 0 Then Return True
    Return SetError($vRet, 0, False)

EndFunc   ;==>_Taskbar_SetOverlayIcon

Func __Taskbar_ErrorHandler()
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
        @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
        @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
        @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
        @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
        @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
        @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
        @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
        @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
        @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc

I think I might be doing something wrong when I'm converting Graphics > Bitmap > Icon > Icon, but I'm not sure what to try instead.

I'm also doing something wrong when I'm centering the string in the circle, but I can figure that out if I spend a little more time on it.

All my code provided is Public Domain... but it may not work. ;) Use it, change it, break it, whatever you want.

Spoiler

My Humble Contributions:
Personal Function Documentation - A personal HelpFile for your functions
Acro.au3 UDF - Automating Acrobat Pro
ToDo Finder - Find #ToDo: lines in your scripts
UI-SimpleWrappers UDF - Use UI Automation more Simply-er
KeePass UDF - Automate KeePass, a password manager
InputBoxes - Simple Input boxes for various variable types

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
  • Recently Browsing   0 members

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