argumentum Posted June 30 Posted June 30 (edited) Spoiler expandcollapse popup#NoTrayIcon #AutoIt3Wrapper_UseX64=y #include <Date.au3> #include <GDIPlus.au3> #include <GUIConstantsEx.au3> #include <GuiMenu.au3> #include <sqlite.au3> #include <Debug.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> ; Required for $SS_LEFTNOWORDWRAP DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("TrayIconHide", 1) Opt("TrayMenuMode", 3) ;~ Global Const $WM_APP = 0x8000 ;~ Global Const $TRAY_CALLBACK_MSG = $WM_APP + 1 Global Const $TRAY_CALLBACK_MSG = 0x8000 + 1 Global $g_iLBUTTONDBLCLK = 0 Global $g_hDummyGUI = GUICreate("TrayHelper") Global $g_iLogToFile = 1 Exit main() Func main() If WinExists('STUB-' & @ScriptName) Then Exit 5 AutoItWinSetTitle('STUB-' & @ScriptName) If $g_iLogToFile Then FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & ".log", @CRLF & _Now() & @CRLF & "Loaded - PID:" & @AutoItPID & @CRLF) Local $iBGColor = 0xFF005500, $iBorderColor = 0xFF349eeb Local $iFGColor = 0xFFE6A800 ; 0xFFFFFFFF ; Default to White text _CreateNumberIcon("X", $iBGColor, $iFGColor, $iBorderColor, "..loading.." & @LF & " ") ;~ _Win11_TrayNotify_HiDPI("Bluetooth", "..loading..", 4000) main_updateTrayWithInfo() AdlibRegister(main_updateTrayWithInfo, 600000) ; 10 min. While 1 Sleep(100) WEnd EndFunc ;==>main Func main_updateTrayWithInfo() Local $hTimer = TimerInit(), $aBluetoothInventory = _Bluetooth_Get_Device_Inventory() Local $sArray = _SQLite_Display2DResult($aBluetoothInventory, 0, True) ConsoleWrite($sArray) If $g_iLogToFile Then FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & ".log", @CRLF & _Now() & @CRLF & $sArray) Local $sText = "?", $iLevel = "?", $iBGColor, $iBorderColor = 0xFF349eeb Local $iFGColor = 0xFFFFFFFF ; Default to White text If UBound($aBluetoothInventory) > 1 Then $iLevel = Int($aBluetoothInventory[1][6]) $sText = $aBluetoothInventory[1][0] & ': ' & $aBluetoothInventory[1][6] & @LF & '(' & $aBluetoothInventory[1][1] & ')' & @LF & ' ' If $iLevel > 99 Then $iLevel = 99 EndIf ; Determine the background color based on the current number If $iLevel >= 50 Then $iBGColor = 0xFF004488 ; BLUE ElseIf $iLevel >= 20 Then $iBGColor = 0xFFE6A800 ; YELLOW $iFGColor = 0xFF000000 ; BLACK text Else $iBGColor = 0xFFD32F2F ; RED EndIf _CreateNumberIcon($iLevel, $iBGColor, $iFGColor, $iBorderColor, $sText) EndFunc ;==>main_updateTrayWithInfo Func _TrayMouseEvents($hWnd, $iMsg, $wParam, $lParam) #forceref $hWnd, $iMsg, $wParam Local Const $WM_LBUTTONUP = 0x0202 Local Const $WM_LBUTTONDBLCLK = 0x0203 Local Const $WM_RBUTTONUP = 0x0205 Switch $lParam Case $WM_LBUTTONUP ConsoleWrite("--> Primary Click Single!" & @CRLF) Return 0 Case $WM_LBUTTONDBLCLK ConsoleWrite("--> Primary Double Click Action!" & @CRLF) ConsoleWrite("Action: You Double-Clicked the custom tray icon!" & @CRLF) AdlibUnRegister(main_updateTrayWithInfo) _CreateNumberIcon("X") $g_iLBUTTONDBLCLK = 1 main_updateTrayWithInfo() AdlibRegister(main_updateTrayWithInfo, 600000) ; 10 min. Return 0 Case $WM_RBUTTONUP ; This handles Secondary Click automatically (Righty OR Lefty!) ConsoleWrite("--> Secondary Click! (Show Context Menu)" & @CRLF) Local $hMenu = _GUICtrlMenu_CreatePopup() _GUICtrlMenu_InsertMenuItem($hMenu, 0, "Browse folder", 1001) _GUICtrlMenu_InsertMenuItem($hMenu, 1, "Reload script", 1002) _GUICtrlMenu_InsertMenuItem($hMenu, 2, "", 0) _GUICtrlMenu_InsertMenuItem($hMenu, 3, "Exit script", 1009) Local $tPoint = DllStructCreate("long X;long Y") DllCall("user32.dll", "bool", "GetCursorPos", "struct*", $tPoint) DllCall("user32.dll", "bool", "SetForegroundWindow", "hwnd", $hWnd) Local $aRet = DllCall("user32.dll", "int", "TrackPopupMenu", "handle", $hMenu, "uint", 0x0100, "int", DllStructGetData($tPoint, "X"), "int", DllStructGetData($tPoint, "Y"), "int", 0, "hwnd", $hWnd, "ptr", 0) Local $iSelectedID = $aRet[0] _GUICtrlMenu_DestroyMenu($hMenu) Switch $iSelectedID Case 1001 AdlibRegister(OnTrayMenu_BrowseScript, 10) Case 1002 ConsoleWrite("ReloadScript..." & @CRLF) AdlibRegister(OnTrayMenu_ReloadScript, 10) Case 1009 AdlibRegister(OnTrayMenu_ExitScript, 10) EndSwitch Return 0 EndSwitch Return $GUI_RUNDEFMSG EndFunc ;==>_TrayMouseEvents Func OnTrayMenu_ExitScript() AdlibUnRegister(OnTrayMenu_ExitScript) AutoItWinSetTitle('EXIT-' & @ScriptName) Exit EndFunc ;==>OnTrayMenu_ExitScript Func OnTrayMenu_BrowseScript() AdlibUnRegister(OnTrayMenu_BrowseScript) Local $iPID = ShellExecute(@WindowsDir & "\explorer.exe", '/n,/select,"' & @ScriptFullPath & '"', "") ConsoleWrite('- $iPID: ' & $iPID & @CRLF) EndFunc ;==>OnTrayMenu_BrowseScript Func OnTrayMenu_ReloadScript() AdlibUnRegister(OnTrayMenu_ReloadScript) AutoItWinSetTitle('RELOAD-' & @ScriptName) ShellExecute(@AutoItExe, '"' & @ScriptFullPath & '"') Exit EndFunc ;==>OnTrayMenu_ReloadScript Func _CreateNumberIcon($iNumber, $iBGColor = 0xFF004488, $iFGColor = 0xFFFFFFFF, $iBorderColor = 0xFF34eb58, $sStr = "") Local Static $iInitGDIPlus = _UpdateTray("StartUp") Local $hBitmap = _GDIPlus_BitmapCreateFromScan0(16, 16) Local $hGraphics = _GDIPlus_ImageGetGraphicsContext($hBitmap) _GDIPlus_GraphicsSetTextRenderingHint($hGraphics, 4) _GDIPlus_GraphicsSetSmoothingMode($hGraphics, 2) _GDIPlus_GraphicsClear($hGraphics, $iBorderColor) Local $hBrushBG = _GDIPlus_BrushCreateSolid($iBGColor) Local $iFillWidth = 1 Local $iFillOtherSide = 13 _GDIPlus_GraphicsFillRect($hGraphics, $iFillWidth, $iFillWidth, $iFillOtherSide, $iFillOtherSide, $hBrushBG) Local $hBrushText = _GDIPlus_BrushCreateSolid($iFGColor) Local $hFamily = _GDIPlus_FontFamilyCreate("Arial") ; Get the current DPI for the primary monitor Local $hDC = _WinAPI_GetDC(0) Local $iDPI_X = _WinAPI_GetDeviceCaps($hDC, 88) ; 88 = LOGPIXELSX _WinAPI_ReleaseDC(0, $hDC) ; Calculate the scaling ratio (96 is the standard 100% DPI) Local $fScale = $iDPI_X / 96 ; Use the scale to adjust your font size dynamically Local $iFontSize = 8 / $fScale Local $hFont = _GDIPlus_FontCreate($hFamily, $iFontSize, 1) Local $hFormat = _GDIPlus_StringFormatCreate() _GDIPlus_StringFormatSetAlign($hFormat, 1) _GDIPlus_StringFormatSetLineAlign($hFormat, 1) Local $tLayout = _GDIPlus_RectFCreate(0, 0, 16, 16) _GDIPlus_GraphicsDrawStringEx($hGraphics, String($iNumber), $hFont, $tLayout, $hFormat, $hBrushText) Local $hGDI_Bitmap = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hBitmap) Local $aMask = DllCall("gdi32.dll", "handle", "CreateBitmap", "int", 16, "int", 16, "uint", 1, "uint", 1, "ptr", 0) Local $hMask = $aMask[0] Local $tICONINFO = DllStructCreate("int fIcon; dword xHotspot; dword yHotspot; handle hbmMask; handle hbmColor") DllStructSetData($tICONINFO, "fIcon", 1) DllStructSetData($tICONINFO, "hbmMask", $hMask) DllStructSetData($tICONINFO, "hbmColor", $hGDI_Bitmap) Local $aIcon = DllCall("user32.dll", "handle", "CreateIconIndirect", "struct*", $tICONINFO) Local $hIcon = $aIcon[0] DllCall("gdi32.dll", "bool", "DeleteObject", "handle", $hMask) DllCall("gdi32.dll", "bool", "DeleteObject", "handle", $hGDI_Bitmap) _GDIPlus_FontDispose($hFont) _GDIPlus_FontFamilyDispose($hFamily) _GDIPlus_StringFormatDispose($hFormat) _GDIPlus_BrushDispose($hBrushText) _GDIPlus_BrushDispose($hBrushBG) _GDIPlus_GraphicsDispose($hGraphics) _GDIPlus_BitmapDispose($hBitmap) _UpdateTray($hIcon, $sStr) DllCall("user32.dll", "bool", "DestroyIcon", "handle", $hIcon) If ($iNumber < 50 Or $g_iLBUTTONDBLCLK) And ($iNumber <> "X") Then _Win11_TrayNotify_HiDPI("Bluetooth", $sStr, 6000, BitAND($iBGColor, 0x00FFFFFF), BitAND($iFGColor, 0x00FFFFFF)) $g_iLBUTTONDBLCLK = 0 ;~ _Win11_TrayNotify_HiDPI($sTitle = "", $sMessage = "", $iDuration = 6000, $iBgColor = 0x704218, $iFgColor = 0xFFFF00) Return 0 EndFunc ;==>_CreateNumberIcon Func _UpdateTray_OnShutdown() _UpdateTray("ShutDown", "") GUIRegisterMsg($TRAY_CALLBACK_MSG, "") If IsHWnd($g_hDummyGUI) Then GUIDelete($g_hDummyGUI) _GDIPlus_Shutdown() EndFunc ;==>_UpdateTray_OnShutdown Func _UpdateTray($hIcon, $sStr = "") Local Static $iStartup = "StartUp", $_bTrayCreated = False, $_tNID = DllStructCreate("dword Size;hwnd Wnd;uint ID;uint Flags;uint CallbackMessage;handle Icon;wchar Tip[128]") If $iStartup = "StartUp" Then DllStructSetData($_tNID, "Size", DllStructGetSize($_tNID)) DllStructSetData($_tNID, "Wnd", $g_hDummyGUI) DllStructSetData($_tNID, "ID", 999) DllStructSetData($_tNID, "Flags", 0x07) DllStructSetData($_tNID, "CallbackMessage", $TRAY_CALLBACK_MSG) GUIRegisterMsg($TRAY_CALLBACK_MSG, "_TrayMouseEvents") _GDIPlus_Startup() OnAutoItExitRegister(_UpdateTray_OnShutdown) $iStartup = "Running" If $hIcon = "StartUp" Then Return 1 ElseIf $hIcon = "ShutDown" Then DllCall("shell32.dll", "bool", "Shell_NotifyIconW", "dword", 0x02, "struct*", $_tNID) ; NIM_DELETE _GDIPlus_Shutdown() Return 2 EndIf DllStructSetData($_tNID, "Icon", $hIcon) DllStructSetData($_tNID, "Tip", String($sStr)) If Not $_bTrayCreated Then DllCall("shell32.dll", "bool", "Shell_NotifyIconW", "dword", 0x00, "struct*", $_tNID) $_bTrayCreated = True Else DllCall("shell32.dll", "bool", "Shell_NotifyIconW", "dword", 0x01, "struct*", $_tNID) EndIf EndFunc ;==>_UpdateTray ; ====================================================================================== Func _Bluetooth_Get_Device_Inventory() Local $a_GetBluetoothDeviceInfo = _GetBluetoothDeviceInfo() Local $aError[1][1] = [["No Bluetooth Radio found or Bluetooth is turned off."]] ; Initialize Radio Local $tFindRadioParams = DllStructCreate("dword dwSize") DllStructSetData($tFindRadioParams, "dwSize", DllStructGetSize($tFindRadioParams)) Local $hRadioOut = 0 Local $aRadioCall = DllCall("Bthprops.cpl", "hwnd", "BluetoothFindFirstRadio", "ptr", DllStructGetPtr($tFindRadioParams), "hwnd*", $hRadioOut) If @error Or $aRadioCall[0] = 0 Then SetError(1) Return $aError EndIf Local $hRadio = $aRadioCall[2] Local $hRadioFindHandle = $aRadioCall[0] ; Search Parameters Local $sSearchParams = "dword dwSize; bool fReturnAuthenticated; bool fReturnRemembered; bool fReturnUnknown; bool fReturnConnected; bool fIssueInquiry; byte cTimeoutMultiplier; hwnd hRadio;" Local $tSearchParams = DllStructCreate($sSearchParams) DllStructSetData($tSearchParams, "dwSize", DllStructGetSize($tSearchParams)) DllStructSetData($tSearchParams, "fReturnAuthenticated", 1) DllStructSetData($tSearchParams, "fReturnRemembered", 1) DllStructSetData($tSearchParams, "fReturnUnknown", 1) DllStructSetData($tSearchParams, "fReturnConnected", 1) DllStructSetData($tSearchParams, "fIssueInquiry", 1) DllStructSetData($tSearchParams, "cTimeoutMultiplier", 4) DllStructSetData($tSearchParams, "hRadio", $hRadio) ; Device Struct Definition Local $sDeviceInfo = "dword dwSize; int64 Address; dword ulClassofDevice; bool fConnected; bool fRemembered; bool fAuthenticated; " & _ "ushort LastSeenYear; ushort LastSeenMonth; ushort LastSeenDayOfWeek; ushort LastSeenDay; ushort LastSeenHour; ushort LastSeenMinute; ushort LastSeenSecond; ushort LastSeenMS; " & _ "ushort LastUsedYear; ushort LastUsedMonth; ushort LastUsedDayOfWeek; ushort LastUsedDay; ushort LastUsedHour; ushort LastUsedMinute; ushort LastUsedSecond; ushort LastUsedMS; " & _ "wchar szName[248];" Local $tDeviceInfo = DllStructCreate($sDeviceInfo) DllStructSetData($tDeviceInfo, "dwSize", DllStructGetSize($tDeviceInfo)) ; Prepare Output Array Structure (Columns: 0 to 7) Local $aResult[1][7] = [["Device Name", "MAC Address (Hex)", "Device Class", "Connected", "Remembered", "Last Used", "Battery Level"]] Local $iDeviceCount = 0 ; Start Device Loop Local $aDeviceCall = DllCall("Bthprops.cpl", "hwnd", "BluetoothFindFirstDevice", "ptr", DllStructGetPtr($tSearchParams), "ptr", DllStructGetPtr($tDeviceInfo)) If @error Or $aDeviceCall[0] = 0 Then DllCall("Bthprops.cpl", "bool", "BluetoothFindRadioClose", "hwnd", $hRadioFindHandle) DllCall("kernel32.dll", "bool", "CloseHandle", "hwnd", $hRadio) Return $aResult Else Local $hDeviceFindHandle = $aDeviceCall[0] Local $sName, $iAddress, $iClass, $bConnected, $bRemembered, $sHexMac, $sFormattedHexMac, $sLastUsed, $sBatteryVal, $iMajorClass While 1 ; Get Data from API $sName = DllStructGetData($tDeviceInfo, "szName") $iAddress = DllStructGetData($tDeviceInfo, "Address") $iClass = DllStructGetData($tDeviceInfo, "ulClassofDevice") $bConnected = DllStructGetData($tDeviceInfo, "fConnected") $bRemembered = DllStructGetData($tDeviceInfo, "fRemembered") $sHexMac = StringLower(Hex($iAddress, 12)) $sFormattedHexMac = StringRegExpReplace($sHexMac, "(.{2})(?=.)", "$1:") ; Parse Timestamp $sLastUsed = "Never" If DllStructGetData($tDeviceInfo, "LastUsedYear") > 0 Then $sLastUsed = DllStructGetData($tDeviceInfo, "LastUsedMonth") & "/" & DllStructGetData($tDeviceInfo, "LastUsedDay") & "/" & DllStructGetData($tDeviceInfo, "LastUsedYear") & _ " " & StringRight("00" & DllStructGetData($tDeviceInfo, "LastUsedHour"), 2) & ":" & StringRight("00" & DllStructGetData($tDeviceInfo, "LastUsedMinute"), 2) EndIf ; Fetch Live Battery if connected $sBatteryVal = "N/A" ; (Offline)" For $iEntry = 0 To UBound($a_GetBluetoothDeviceInfo) - 1 If $a_GetBluetoothDeviceInfo[$iEntry][6] = $sFormattedHexMac Then $sBatteryVal = $a_GetBluetoothDeviceInfo[$iEntry][1] Next ; Major Device Class occupies bits 8 to 12 $iMajorClass = BitShift(BitAND($iClass, 0x1F00), 8) ;~ If $iMajorClass = 4 And $bRemembered Then If $iMajorClass = 4 And $bConnected Then $iDeviceCount += 1 ReDim $aResult[$iDeviceCount + 1][UBound($aResult, 2)] $aResult[$iDeviceCount][0] = $sName $aResult[$iDeviceCount][1] = StringUpper($sFormattedHexMac) $aResult[$iDeviceCount][2] = "0x" & Hex($iClass, 6) $aResult[$iDeviceCount][3] = ($bConnected ? "True" : "False") $aResult[$iDeviceCount][4] = ($bRemembered ? "True" : "False") $aResult[$iDeviceCount][5] = $sLastUsed $aResult[$iDeviceCount][6] = $sBatteryVal ; << don't work here EndIf ; Next Device Local $aNextCall = DllCall("Bthprops.cpl", "bool", "BluetoothFindNextDevice", "hwnd", $hDeviceFindHandle, "ptr", DllStructGetPtr($tDeviceInfo)) If @error Or $aNextCall[0] = 0 Then ExitLoop WEnd DllCall("Bthprops.cpl", "bool", "BluetoothFindDeviceClose", "hwnd", $hDeviceFindHandle) EndIf ; Cleanup DllCall("Bthprops.cpl", "bool", "BluetoothFindRadioClose", "hwnd", $hRadioFindHandle) DllCall("kernel32.dll", "bool", "CloseHandle", "hwnd", $hRadio) Return $aResult EndFunc ;==>_Bluetooth_Get_Device_Inventory Func _GetBluetoothDeviceInfo() Local Const $DIGCF_PRESENT = 0x00000002 Local Const $DIGCF_DEVICEINTERFACE = 0x00000010 ; Define properties to query: {GUID}, Property ID Local $aKeys[5][2] = [ _ ["{a45c254e-df1c-4efd-8020-67d146a850e0}", 14], _ ; Friendly Name ["{104EA319-6EE2-4701-BD47-8DDBF425BBE5}", 2], _ ; Battery Level ["{a45c254e-df1c-4efd-8020-67d146a850e0}", 13], _ ; Manufacturer (May still be blank for Generic BT devices!) ["{a45c254e-df1c-4efd-8020-67d146a850e0}", 3], _ ; Hardware ID ["{78c34fc8-104a-4aca-9ea4-524d52996e57}", 256] _ ; Instance ID ] Local $iNumProps = UBound($aKeys) ; Bluetooth Device Interface Class GUID Local $tGUID = DllStructCreate("ulong;ushort;ushort;byte[8]") DllCall("ole32.dll", "long", "CLSIDFromString", "wstr", "{0000111e-0000-1000-8000-00805f9b34fb}", "ptr", DllStructGetPtr($tGUID)) ; Handle to active Bluetooth objects Local $aHdev = DllCall("setupapi.dll", "handle", "SetupDiGetClassDevsW", _ "ptr", DllStructGetPtr($tGUID), "ptr", 0, "hwnd", 0, "dword", BitOR($DIGCF_PRESENT, $DIGCF_DEVICEINTERFACE)) If @error Or $aHdev[0] = -1 Then Return SetError(1, 0, 0) Local $hDevInfo = $aHdev[0] ; Explicit structural memory boundaries for 64-bit portability Local $sInterfaceDataDef = "dword cbSize;ulong;ushort;ushort;byte[8];ulong_ptr Reserved" Local $tDeviceInterfaceData = DllStructCreate($sInterfaceDataDef) DllStructSetData($tDeviceInterfaceData, "cbSize", DllStructGetSize($tDeviceInterfaceData)) Local $sDevInfoDataDef = "dword cbSize;ulong;ushort;ushort;byte[8];ulong_ptr DevInst" Local $tDeviceInfoData = DllStructCreate($sDevInfoDataDef) DllStructSetData($tDeviceInfoData, "cbSize", DllStructGetSize($tDeviceInfoData)) Local $iIndex = 0 Local $aResults[50][$iNumProps] Local $iCount = 0 While True Local $aEnum = DllCall("setupapi.dll", "bool", "SetupDiEnumDeviceInterfaces", _ "handle", $hDevInfo, "ptr", 0, "ptr", DllStructGetPtr($tGUID), "dword", $iIndex, "ptr", DllStructGetPtr($tDeviceInterfaceData)) If @error Or Not $aEnum[0] Then ExitLoop ; Match interface to specific hardware instance block DllCall("setupapi.dll", "bool", "SetupDiGetDeviceInterfaceDetailW", _ "handle", $hDevInfo, "ptr", DllStructGetPtr($tDeviceInterfaceData), "ptr", 0, "dword", 0, "dword*", 0, "ptr", DllStructGetPtr($tDeviceInfoData)) Local $bHasData = False ; Loop through our mapped DEVPROPKEYs For $i = 0 To $iNumProps - 1 Local $tPropKey = DllStructCreate("ulong;ushort;ushort;byte[8];ulong") DllCall("ole32.dll", "long", "CLSIDFromString", "wstr", $aKeys[$i][0], "ptr", DllStructGetPtr($tPropKey)) DllStructSetData($tPropKey, 5, $aKeys[$i][1]) ; Use a generic byte buffer large enough for any standard type Local $tBuffer = DllStructCreate("byte[1024]") Local $iPropType = 0 Local $iReqSize = 0 ; Extract Property Local $aProp = DllCall("setupapi.dll", "bool", "SetupDiGetDevicePropertyW", _ "handle", $hDevInfo, "ptr", DllStructGetPtr($tDeviceInfoData), "ptr", DllStructGetPtr($tPropKey), _ "ulong*", $iPropType, "ptr", DllStructGetPtr($tBuffer), "dword", 1024, "dword*", $iReqSize, "dword", 0) Local $sValue = "" ; If extraction succeeded, cast the memory dynamically based on the returned PropertyType If Not @error And $aProp[0] Then $iPropType = $aProp[4] ; $aProp[4] holds the type of data Windows returned If $iPropType = 0x00000012 Or $iPropType = 0x00002012 Then ; DEVPROP_TYPE_STRING or DEVPROP_TYPE_STRING_LIST Local $tString = DllStructCreate("wchar[512]", DllStructGetPtr($tBuffer)) $sValue = DllStructGetData($tString, 1) ElseIf $iPropType = 0x00000003 Then ; DEVPROP_TYPE_BYTE (Used for Battery Levels) $sValue = DllStructGetData($tBuffer, 1, 1) If $sValue >= 0 And $sValue <= 100 Then $sValue &= "%" ElseIf $iPropType = 0x00000007 Then ; DEVPROP_TYPE_UINT32 Local $tInt = DllStructCreate("dword", DllStructGetPtr($tBuffer)) $sValue = DllStructGetData($tInt, 1) EndIf If $sValue <> "" Then $bHasData = True EndIf $aResults[$iCount][$i] = (StringLen($sValue) < 2 ? "N/A" : $sValue) Next ; Only move to the next row if the device actually had populated metadata If $bHasData Then $iCount += 1 ; Dynamically resize array if you have more than 50 Bluetooth components If $iCount >= UBound($aResults) Then ReDim $aResults[UBound($aResults) + 20][$iNumProps] $iIndex += 1 WEnd DllCall("setupapi.dll", "bool", "SetupDiDestroyDeviceInfoList", "handle", $hDevInfo) If $iCount = 0 Then Return SetError(2, 0, 0) ReDim $aResults[$iCount][$iNumProps + 2] Local $aMatch, $sFormattedMAC ; get MAC from InstanceID For $n = 0 To UBound($aResults) - 1 $aMatch = StringRegExp($aResults[$n][$iNumProps - 1], "&([0-9A-Fa-f]{12})_", 1) If @error Then ContinueLoop $sFormattedMAC = StringRegExpReplace($aMatch[0], "(.{2})(?!$)", "$1:") If @error Then ContinueLoop $aResults[$n][$iNumProps + 1] = $sFormattedMAC Next Return $aResults EndFunc ;==>_GetBluetoothDeviceInfo ; ====================================================================================== ;~ ConsoleWrite("DeviceClass desc.: " & _DecodeDeviceClass(0x240404) & @CRLF) Func _DecodeDeviceClass($iCoD) ;~ Local $iCoD = 0x240404 ;~ ; Major Service Classes (Bits 13 to 23) ;~ Local $iServiceClass = BitAND($iCoD, 0xFFE000) ;~ ; Major Device Class (Bits 8 to 12) ;~ Local $iMajorClass = BitShift(BitAND($iCoD, 0x1F00), 8) ; Will return 4 for Audio/Video ;~ ; Minor Device Class (Bits 2 to 7) ;~ Local $iMinorClass = BitShift(BitAND($iCoD, 0xFC), 2) ; Will return 1 for Headset, 6 for Headphones Local $iMajorClass = BitShift(BitAND($iCoD, 0x1F00), 8) Local $iMinorClass = BitShift(BitAND($iCoD, 0x00FC), 2) Local $sMajorText = "Unknown" Local $sMinorText = "Generic" ; Look-up for Major Device Class Switch $iMajorClass Case 1 $sMajorText = "Computer" Case 2 $sMajorText = "Phone" Case 3 $sMajorText = "Network Access Point" Case 4 $sMajorText = "Audio/Video" Case 5 $sMajorText = "Peripheral (Mouse/Keyboard)" Case 6 $sMajorText = "Imaging (Printer/Scanner)" Case 7 $sMajorText = "Wearable" EndSwitch ; Look-up for Minor Device Class ( for Audio/Video family ) If $iMajorClass = 4 Then Switch $iMinorClass Case 1 $sMinorText = "Wearable Headset" Case 2 $sMinorText = "Hands-free Device" Case 4 $sMinorText = "Microphone" Case 5 $sMinorText = "Loudspeaker" Case 6 $sMinorText = "Headphones" Case 7 $sMinorText = "Portable Audio" Case 11 $sMinorText = "Set-top Box" Case 12 $sMinorText = "HiFi Audio Device" Case 13 $sMinorText = "VCR/DVD/BlueRay" Case 14 $sMinorText = "Video Camera" Case 15 $sMinorText = "Display/Monitor and Loudspeaker" EndSwitch EndIf Return $sMajorText & " -> " & $sMinorText EndFunc ;==>_DecodeDeviceClass ; ====================================================================================== ;~ _Win11_TrayNotify_HiDPI("HiDPI Alert", "yes, this is an alert. Be alerted !", 4000) ; close it by ESC or mouse click on it ( left or right ) Func _Win11_TrayNotify_HiDPI($sTitle = "", $sMessage = "", $iDuration = 6000, $iBGColor = 0x704218, $iFGColor = 0xFFFF00) Local Static $hNotifyGUI = 0, $h_Timer, $i_Duration = -2, $i_Clicked1 = 0, $i_Clicked2 ; removed the "_HiDPI_" stuff, If Eval("sTitle") = "" And Eval("sMessage") = "" Then ; Eval() is needed because of Adlib ;) Local $aMouse = MouseGetPos() Local $aWinPos = WinGetPos($hNotifyGUI) Local $aRet = DllCall("user32.dll", "short", "GetAsyncKeyState", "int", 0x1B) ; 0x1B = VK_ESCAPE If Not @error And $i_Clicked1 = 0 And BitAND($aRet[0], 0x8000) Then $i_Clicked1 = 1 If IsArray($aMouse) And IsArray($aWinPos) Then If $aMouse[0] >= $aWinPos[0] And $aMouse[0] <= ($aWinPos[0] + $aWinPos[2]) And _ $aMouse[1] >= $aWinPos[1] And $aMouse[1] <= ($aWinPos[1] + $aWinPos[3]) Then $aRet = DllCall("user32.dll", "short", "GetAsyncKeyState", "int", 0x01) ; 0x01 = VK_LBUTTON If Not @error Then If $i_Clicked1 = 0 And BitAND($aRet[0], 0x8000) Then $i_Clicked1 = 2 If $i_Clicked1 = 2 And Not BitAND($aRet[0], 0x8000) Then $i_Clicked1 = 1 EndIf $aRet = DllCall("user32.dll", "short", "GetAsyncKeyState", "int", 0x02) ; 0x02 = VK_RBUTTON If Not @error Then If $i_Clicked2 = 0 And BitAND($aRet[0], 0x8000) Then $i_Clicked2 = 2 If $i_Clicked2 = 2 And Not BitAND($aRet[0], 0x8000) Then $i_Clicked2 = 1 EndIf EndIf EndIf If $i_Clicked1 = 1 Or $i_Clicked2 = 1 Or TimerDiff($h_Timer) >= $i_Duration Then AdlibUnRegister("_Win11_TrayNotify_HiDPI") If GUIDelete($hNotifyGUI) Then $hNotifyGUI = 0 EndIf Return EndIf AdlibUnRegister("_Win11_TrayNotify_HiDPI") If IsHWnd($hNotifyGUI) Then GUIDelete($hNotifyGUI) Local $hDC = DllCall("user32.dll", "handle", "GetDC", "hwnd", 0) Local $iLogPixelsY = DllCall("gdi32.dll", "int", "GetDeviceCaps", "handle", $hDC[0], "int", 90) DllCall("user32.dll", "int", "ReleaseDC", "hwnd", 0, "handle", $hDC[0]) Local $fScale = $iLogPixelsY[0] / 96 $fScale = 1 ; remove this line if you're gonna use "_HiDPI_" stuff $i_Duration = $iDuration $i_Clicked1 = 0 $i_Clicked2 = 0 Local $iWidth = 300 Local $iHeight = 85 $hNotifyGUI = GUICreate("_Win11_TrayNotify_HiDPI|PID:" & @AutoItPID, $iWidth, $iHeight, -1, -1, 0x80880000, -1, WinGetHandle('STUB-' & @ScriptName)) GUISetBkColor($iBGColor, $hNotifyGUI) GUICtrlCreateLabel($sTitle, 15, 10, $iWidth - 30, 27, $SS_LEFTNOWORDWRAP) GUICtrlSetColor(-1, $iFGColor) GUICtrlSetFont(-1, 10, 800, 0, "Segoe UI") GUICtrlCreateLabel($sMessage, 15, 45, $iWidth - 30, 30, $SS_LEFTNOWORDWRAP) GUICtrlSetColor(-1, $iFGColor) GUICtrlSetFont(-1, 9, 400, 0, "Segoe UI") Local $iScaledWidth = $iWidth * $fScale Local $iScaledHeight = $iHeight * $fScale Local $iPadding = 10 * $fScale Local $iX = @DesktopWidth - $iScaledWidth - $iPadding Local $iY = @DesktopHeight - 100 - $iScaledHeight - $iPadding Local $aTrayPos = WinGetPos("[CLASS:Shell_TrayWnd]") If IsArray($aTrayPos) Then $iY = @DesktopHeight - $aTrayPos[3] - $iScaledHeight - $iPadding WinMove($hNotifyGUI, "", $iX, $iY) GUISetState(@SW_SHOWNOACTIVATE, $hNotifyGUI) WinSetOnTop($hNotifyGUI, "", 1) $h_Timer = TimerInit() AdlibRegister("_Win11_TrayNotify_HiDPI", 100) ; 100 mSec. should be ok EndFunc ;==>_Win11_TrayNotify_HiDPI I use earbuds ( these cheaper ones because they don't bother my ears** ) and the internal batteries of each side are different. Meaning that one always dies hours before the other and I wanted a "tray indicator" because the build-in on windows one takes clicking, and the "battery low" announcement annoys me when am not close to the PC. Now if I happen to see the battery at 40% or less I know that might as well swap them with my other set of earbuds if I feel like it. ** since I don't inset them in my ear canal ( just let them loose ), I also have a sound equalizer ( FxSound ) to make them sound better. Code wise here are examples of a fake tray icon with clicking and Bluetooth radio, and other Bluetooth things if you scrape them out of the script. It checks every 10 min. P.S.: Was thinking of adding a "on new devise connected" thing, and if I ever do, I'll update the example. Edit 2026.07.08: Added a popup/tray thing to make it more visible that the battery is 40% or below. Added update on tray DClick Edited July 9 by argumentum newer Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
argumentum Posted July 9 Author Posted July 9 (edited) On 6/30/2026 at 3:59 PM, argumentum said: Was thinking of adding a "on new devise connected" thing, and if I ever do, I'll update the example. Am not gonna use it but you might expandcollapse popup#AutoIt3Wrapper_UseX64=y #AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7 #include <ListViewConstants.au3> #include <EditConstants.au3> #include <GUIConstantsEx.au3> #include <WindowsConstants.au3> #include <GuiListView.au3> #include <GUIDarkTheme.au3> ; https://www.autoitscript.com/forum/topic/213613-guidarktheme-udf/ #include <GuiCtrls_HiDpi.au3> ; https://www.autoitscript.com/forum/topic/210613-guictrls_hidpi-udf-in-progress/ _HiDpi_Ctrl_LazyInit() ;~ Global Const $WM_DEVICECHANGE = 0x0219 Global Const $DBT_DEVICEARRIVAL = 0x8000 Global Const $DBT_DEVICEREMOVECOMPLETE = 0x8004 ; A device or piece of media has been removed. ; https://learn.microsoft.com/en-us/windows/win32/devio/wm-devicechange Global Const $DBT_DEVTYP_DEVICEINTERFACE = 0x00000005 Global $g_hGUI, $g_idListView, $g_hDeviceNotify _GUI_Create() _Reg_DetailedDeviceNotifications() _ConsoleListviewWrite("- Ready.") While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE Exit ; will automatically trigger the cleanup function EndSwitch WEnd Func WM_DEVICECHANGE_Handler($hWnd, $iMsg, $wParam, $lParam) _ConsoleListviewWrite('+ Func WM_DEVICECHANGE_Handler(' & $hWnd & ', ' & $iMsg & ', ' & $wParam & ', ' & $lParam & ')' & @CRLF) If $wParam = $DBT_DEVICEARRIVAL Or $wParam = $DBT_DEVICEREMOVECOMPLETE Then Local $tHeader = DllStructCreate("dword Size; dword DeviceType; dword Reserved", $lParam) If DllStructGetData($tHeader, "DeviceType") = $DBT_DEVTYP_DEVICEINTERFACE Then Local $iTotalSize = DllStructGetData($tHeader, "Size") Local $iStringLen = ($iTotalSize - 28) / 2 Local $tInterface = DllStructCreate("dword Size; dword DeviceType; dword Reserved; byte ClassGuid[16]; wchar Name[" & $iStringLen & "]", $lParam) Local $sDevicePath = DllStructGetData($tInterface, "Name") If StringInStr($sDevicePath, "MMDEVAPI") Then ; Extract the endpoint GUID block between the brackets: {d448e9f2-5211-490a-bef1-58b9f9cc4881} ; Match standard 8-4-4-4-12 hex pattern inside curly braces Local $aGuidMatch = StringRegExp($sDevicePath, "(?i)\{([[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})\}", 3) If UBound($aGuidMatch) > 0 Then ; The audio endpoint subkey format uses the full curly braces Local $sEndpointGUID = "{" & $aGuidMatch[0] & "}" Local $sRoot = (@AutoItX64) ? "HKLM" : "HKLM64" Local $sParentKey = $sRoot & "\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\" & $sEndpointGUID Local $sPropsKey = $sParentKey & "\Properties" ; Windows Audio Properties subkey for the endpoint ;~ Local $sFxPropsKey = $sParentKey & "\FxProperties" ; meh _ConsoleListviewWrite(" + $sPropsKey: " & $sPropsKey & @CRLF) ; PKEY_Device_FriendlyName (Native Windows Audio property key) Local $sFriendlyName = RegRead($sPropsKey, "{a45c254e-df1c-4efd-8020-67d146a850e0},2") ; PKEY_Device_EnumeratorName (tells us if it's SWD, USB, BTHENUM, etc.) ; didn't test much Local $sEnumerator = RegRead($sPropsKey, "{a45c254e-df1c-4efd-8020-67d146a850e0},24") _ConsoleListviewWrite(" + $sEnumerator: " & $sEnumerator & @CRLF) _ConsoleListviewWrite(" + $sFriendlyName: " & $sFriendlyName & @CRLF) ; SKIP BLANK ENTRIES ( race condition protection ? ) If $sFriendlyName = "" And $sEnumerator = "" Then Return $GUI_RUNDEFMSG Local $sEvent = ($wParam = $DBT_DEVICEARRIVAL) ? "Connected" : "Disconnected" ; Check if the underlying enumerator or description points to Bluetooth If StringInStr($sEnumerator, "BTH") Or StringInStr($sFriendlyName, "Bluetooth") Then _ConsoleListviewWrite("--> Bluetooth Audio " & $sEvent & ": " & $sFriendlyName & @CRLF) ; ========================================================================= ; YOUR CUSTOM CODE GOES HERE ; e.g., If $sEvent = "Connected" Then _DoSomething() ; I would use AdlibRegister(,1000) for the possible race condition ; ========================================================================= ; Read raw values Local $iRawFormFactor = RegRead($sPropsKey, "{a45c254e-df1c-4efd-8020-67d146a850e0},0") Local $iRawState = RegRead($sParentKey, "DeviceState") ; Translate to human readable becuse we are nice people Local $sFormFactorReadable = _GetAudioFormFactorName($iRawFormFactor) Local $sStateReadable = _GetAudioDeviceStateName($iRawState) _ConsoleListviewWrite(" + Hardware Profile: " & $sFormFactorReadable & @CRLF) _ConsoleListviewWrite(" + Current Status: " & $sStateReadable & @CRLF) EndIf EndIf EndIf EndIf EndIf Return $GUI_RUNDEFMSG EndFunc ;==>WM_DEVICECHANGE_Handler ; ========================================================================= ; Desc: Translates Windows EndpointFormFactor DWORD into readable text ; ========================================================================= Func _GetAudioFormFactorName($iFormFactor) Switch Int($iFormFactor) Case 0 Return "Remote Device" Case 1 Return "Speakers" Case 2 Return "Headphones" Case 3 Return "Earphones" Case 4 Return "Built-in Microphone Array" Case 5 Return "Headset (Microphone + Playback)" Case 6 Return "Handset" Case 7 Return "Digital Audio Interface (SPDIF/HDMI)" Case 8 Return "Line Level In/Out (Aux)" Case 9 Return "SPDIF Interface" Case 10 Return "HDMI Device" Case 11 Return "Unknown Form Factor" Case Else Return "Undefined (" & $iFormFactor & ")" EndSwitch EndFunc ;==>_GetAudioFormFactorName ; ========================================================================= ; Desc: Translates Windows MMDevice DEVICE_STATE_xxx constants ; ========================================================================= Func _GetAudioDeviceStateName($iState) Switch Int($iState) Case 1 ; DEVICE_STATE_ACTIVE Return "Active / Connected" Case 2 ; DEVICE_STATE_DISABLED Return "Disabled by User" Case 4 ; DEVICE_STATE_NOTPRESENT Return "Hardware Unplugged / Missing" Case 8 ; DEVICE_STATE_UNPLUGGED Return "Jack Unplugged" Case 26843546 ; Common combined state for certain disabled virtual profiles Return "Disabled (Unavailable)" Case Else Return "Unknown Status (" & $iState & ")" EndSwitch EndFunc ;==>_GetAudioDeviceStateName Func _ConsoleListviewWrite($sStr) Local $iBgColor, $sTime = @HOUR & ":" & @MIN & ":" & @SEC & "." & @MSEC $sStr &= (StringRight($sStr, 2) = @CRLF ? "" : @CRLF) ConsoleWrite($sStr) FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & "_" & @YEAR & @MON & @MDAY & ".log", $sTime & " | " & $sStr) Switch StringLeft($sStr, 1) Case "+" ; Success -> Green-ish ;~ $iBgColor = 0xD4EDDA $iBgColor = 0x0F3D1A $sStr = StringTrimLeft($sStr, 1) ; Strip the "+" Case "!" ; Error -> Red-ish ;~ $iBgColor = 0xF8D7DA $iBgColor = 0x5C1D24 $sStr = StringTrimLeft($sStr, 1) ; Strip the "!" Case ">", "-" ; Warning/Info -> Yellow/Orange-ish ;~ $iBgColor = 0xFFF3CD $iBgColor = 0x5C4308 $sStr = StringTrimLeft($sStr, 1) ; Strip the symbol Case Else ;~ $iBgColor = 0x212529 $iBgColor = 0x121212 EndSwitch GUICtrlSetBkColor(GUICtrlCreateListViewItem($sTime & "|" & StringStripWS($sStr, 1), $g_idListView), $iBgColor) ; Auto-scroll to the bottom GUICtrlSendMsg($g_idListView, $LVM_ENSUREVISIBLE, GUICtrlSendMsg($g_idListView, $LVM_GETITEMCOUNT, 0, 0) - 1, 0) EndFunc ;==>_ConsoleListviewWrite Func _GUI_Create() Local $iFormW = 600, $iFormH = 400 $g_hGUI = _HiDPI_GUICreate("Bluetooth Monitor", $iFormW, $iFormH, -1, -1, BitOR($GUI_SS_DEFAULT_GUI, $WS_MAXIMIZEBOX, $WS_SIZEBOX, $WS_THICKFRAME, $WS_TABSTOP)) GUIRegisterMsg($WM_DEVICECHANGE, "WM_DEVICECHANGE_Handler") $g_idListView = _HiDPI_GUICtrlCreateListView("Time|Message", 0, 0, $iFormW, $iFormH, -1, BitOR($LVS_EX_FULLROWSELECT, $WS_VSCROLL)) GUICtrlSetResizing(-1, $GUI_DOCKLEFT + $GUI_DOCKRIGHT + $GUI_DOCKTOP + $GUI_DOCKBOTTOM) GUICtrlSendMsg($g_idListView, $LVM_SETCOLUMNWIDTH, 0, _HiDpi_CtrlAdjRatio(80)) GUICtrlSendMsg($g_idListView, $LVM_SETCOLUMNWIDTH, 1, @DesktopWidth) _GUIDarkTheme_ApplyDark($g_hGUI) GUISetState(@SW_SHOW) EndFunc ;==>_GUI_Create Func _Reg_DetailedDeviceNotifications() ; Convert the Bluetooth Device Class GUID into a binary structure. ; GUID for Bluetooth Devices: {0850302A-B344-4FDA-9BE9-90576B8D46F0} Local $sGUID = "{0850302A-B344-4FDA-9BE9-90576B8D46F0}" Local $tGUID = DllStructCreate("byte[16]") DllCall("ole32.dll", "hresult", "CLSIDFromString", "wstr", $sGUID, "struct*", $tGUID) ; Create the DEV_BROADCAST_DEVICEINTERFACE structure for the registration request. ; Note: We only need Name[1] here because we are just sending the header to Windows, not reading from it yet. Local $tDevInterface = DllStructCreate("dword Size; dword DeviceType; dword Reserved; byte ClassGuid[16]; wchar Name[1]") DllStructSetData($tDevInterface, "Size", DllStructGetSize($tDevInterface)) DllStructSetData($tDevInterface, "DeviceType", $DBT_DEVTYP_DEVICEINTERFACE) DllStructSetData($tDevInterface, "ClassGuid", DllStructGetData($tGUID, 1)) Local $aRet = DllCall("user32.dll", "ptr", "RegisterDeviceNotificationW", "hwnd", $g_hGUI, "struct*", $tDevInterface, "dword", 4) ; 0) ; ; 0x00000004 is DEVICE_NOTIFY_ALL_INTERFACE_CLASSES If @error Or Not IsArray($aRet) Or Not $aRet[0] Then MsgBox(16, "Error", "Failed to register for detailed device notifications.") Exit EndIf $g_hDeviceNotify = $aRet[0] OnAutoItExitRegister("_Cleanup_DeviceNotification") EndFunc ;==>_Reg_DetailedDeviceNotifications Func _Cleanup_DeviceNotification() ; Check if the handle exists before attempting to unregister it If IsDeclared("g_hDeviceNotify") And $g_hDeviceNotify Then _ConsoleListviewWrite("--> unregistering device notification hook" & @CRLF) DllCall("user32.dll", "bool", "UnregisterDeviceNotification", "ptr", $g_hDeviceNotify) $g_hDeviceNotify = 0 ; Clear the handle EndIf If IsHWnd($g_hGUI) Then If GUIDelete($g_hGUI) Then $g_hGUI = 0 EndIf EndFunc ;==>_Cleanup_DeviceNotification Edited July 9 by argumentum Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now