Skip to content

Commit

Permalink
BoseQCMicMute v1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
aderusha committed Mar 24, 2022
1 parent aa42af7 commit 7730cc8
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 67 deletions.
Binary file modified BoseQCMicMute.exe
Binary file not shown.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,34 @@ Changes in mute status will alert the user in the following ways:
* A Windows notification is briefly shown
* System tray icon and icon hover text (white icon = mic active, black icon = mic muted)

Audio and visual notifications can be enabled or disabled by right-clicking on the tray icon.

---

## How to install Bose QC Mic Mute

1. Download the latest release
2. Unzip to a folder and double-click `BoseQCMicMute.exe` to launch
1. Download `BoseQCMicMute.exe` and run it. That's it!

---

## Changelog

### v1.0.0
* Initial release

### v1.1.0
* Single-file .exe, just download the file and run it wherever!
* Add options to enable or disable audio and visual alerts
* More aggressive hooking and re-hooking of media keys

---

## Additional notes

* **WINDOWS DEFENDER VIRUS DETECTED** - This is common for applications developed with AutoHotKey as it needs to monitor keyboard activity to catch the media keys being pressed. That makes it look like a keylogger and may throw an error on download or when extracting the files. If you don't trust me, just [download the source](https://github.com/aderusha/BoseQCMicMute/releases) and run it with [AutoHotkey v2](https://www.autohotkey.com/v2/).
* The headphones send media keys which are sometimes captured by media player software. That behavior can prevent this tool from working. [See this article](https://www.askvg.com/fix-media-keys-not-working-in-spotify-itunes-and-other-media-players/) for details on how you might deal with this.
* **WINDOWS DEFENDER VIRUS DETECTED** - This is common for applications developed with AutoHotKey as it needs to monitor keyboard activity to catch the media keys being pressed. That makes it look like a keylogger and may throw an error on download. If you don't trust me, just [download the source](https://github.com/aderusha/BoseQCMicMute/source) and compile it with [AutoHotkey v2](https://www.autohotkey.com/v2/).
* The headphones send media keys which are sometimes captured by media player software. That behavior can prevent this tool from working. [See this article](https://www.askvg.com/fix-media-keys-not-working-in-spotify-itunes-and-other-media-players/) for details on how you might deal with this. Version 1.1 includes several new features to address this issue.
* If you want to modify the source code, keep in mind that this is developed with [AutoHotkey v2](https://www.autohotkey.com/v2/). The code will not work with v1 (which doesn't support modern sound interface access), so make sure you grab the right version.
* The AHK script attempts to load icon resources from itself, which will only work when running as the compiled .exe. Running as an AHK2 script without compiling will not work.

---

Expand Down
214 changes: 151 additions & 63 deletions BoseQCMicMute.ah2 → src/BoseQCMicMute.ah2
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; BoseQCMicMute v1.0.0
;; BoseQCMicMute v1.1.0
;;
;; Control microphone mute on Bose QC35 and QC45 headphones by pressing the
;; "action" button located between the volume buttons on the right earcup.
Expand All @@ -10,7 +10,7 @@
;; triple press == Media_Prev == Unmute all microphone devices
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Copyright (c) 2021 Allen Derusha [email protected]
;; Copyright (c) 2022 Allen Derusha [email protected]
;;
;; Licensed under MIT License
;;
Expand All @@ -35,110 +35,118 @@

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Global variables
softwareVersion := "1.0.0" ; Current release version
softwareVersion := "1.1.0" ; Current release version
beepMuteFreq := 350 ; Frequency in Hertz of tone played when muting
beepMuteDuration := 500 ; Duration in msec of tone played when muting
beepUnmuteFreq := 700 ; Frequency in Hertz of tone played when unmuting
beepUnmuteDuration := 500 ; Duration in msec of tone played when unmuting
iconMutedFilename := A_ScriptDir . "\MicMuted.ico" ; Filename for icon shown while muted
iconMutedResource := 1 ; Resource ID for icon shown while muted
iconUnmutedFilename := A_ScriptDir . "\MicUnmuted.ico" ; Filename for icon shown while unmuted
iconUnmutedResource := 1 ; Resource ID for icon shown while munuted
beepEnabled := 1
toastEnabled := 1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Compiler directives
#NoTrayIcon ; Hide our tray icon until we determine current mute state
;@Ahk2Exe-AddResource MicUnmuted.ico, MICUNMUTED
;@Ahk2Exe-AddResource MicMuted.ico, MICMUTED
;@Ahk2Exe-AddResource MicUnmuted.ico, 1
;@Ahk2Exe-AddResource MicMuted.ico, 2
;@Ahk2Exe-SetMainIcon MicUnmuted.ico
;@Ahk2Exe-SetCompanyName [email protected]
;@Ahk2Exe-SetCopyright Copyright (c) 2021 Allen Derusha [email protected]
;@Ahk2Exe-SetVersion 1.0.0
;@Ahk2Exe-SetCopyright Copyright (c) 2022 Allen Derusha [email protected]
;@Ahk2Exe-SetVersion 1.1.0
;@Ahk2Exe-SetName Bose QC Mic Mute
;@Ahk2Exe-SetDescription Control microphone mute on Bose QC35 and QC45 headphones

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Load icon resources from ourself
MICUNMUTED := LoadPicture(A_ScriptName, "Icon1", &IconType)
MICMUTED := LoadPicture(A_ScriptName, "Icon2", &IconType)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Load saved settings from the registry
ReadSavedSettings()

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create taskbar menu
A_TrayMenu.Delete() ; Remove the standard menu items
A_TrayMenu.Add("&About BoseQCMicMute", MenuHandlerAbout)
A_TrayMenu.Add()
A_TrayMenu.Add("Microphones are:", MenuHandlerMuteToggle)
A_TrayMenu.Add()
A_TrayMenu.Add("Enable toast notification", MenuHandlerEnableToast)
A_TrayMenu.Add("Disable toast notification", MenuHandlerDisableToast)
A_TrayMenu.Add()
A_TrayMenu.Add("Enable audio notification", MenuHandlerEnableBeep)
A_TrayMenu.Add("Disable audio notification", MenuHandlerDisableBeep)
A_TrayMenu.Add()
A_TrayMenu.Add("&Mute all microphones", MenuHandlerMuteAll)
A_TrayMenu.Add("&Unmute all microphones", MenuHandlerUnMuteAll)
A_TrayMenu.Add("&Toggle mute status", MenuHandlerMuteToggle)
A_TrayMenu.Add()
A_TrayMenu.Add("E&xit BoseQCMicMute", MenuHandlerExit)
A_TrayMenu.Default := "&Toggle mute status" ; Set default action to toggle mute
A_TrayMenu.ClickCount := 1 ; Single-click icon to trigger default action
if (beepEnabled = 1) { ; Place checkmark for beepEnabled
A_TrayMenu.Check("Enable audio notification")
} else {
A_TrayMenu.Check("Disable audio notification")
}
if (toastEnabled = 1) { ; Place checkmark for toastEnabled
A_TrayMenu.Check("Enable toast notification")
} else {
A_TrayMenu.Check("Disable toast notification")
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Collect the mute status of the first audio capture device found, declare that
;; our "Global" state, and set everything else to match
micMute := GetMicMute()
if (micMute = 0)
{ ; the first device we found was unmuted, so set all other devices unmuted
if (micMute = 0) { ; the first device we found was unmuted, so set all other devices unmuted
TraySetIcon "HICON:" MICUNMUTED
SetGlobalMicMute(0)
} else
{ ; the first device we found was muted, so set all other devices muted
} else {
TraySetIcon "HICON:" MICMUTED
SetGlobalMicMute(1)
}
A_IconHidden := false ; Show our tray icon now that our mute state is sorted

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Show tray icon now that everything is sorted out
A_IconHidden := 0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Setup keyboard hook and a timer to re-hook our hotkeys every 15 seconds
#UseHook true
InstallKeybdHook(true, true)
SetTimer ReHookHotkeys, 15000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Hotkeys
Media_Play_Pause::
{ ; Single press of action button
Media_Play_Pause:: { ; Single press of action button: Toggle mute state
ToggleGlobalMicMute
}

Media_Next::
{ ; Double press of action button
Media_Next:: { ; Double press of action button: Mute all mics
SetGlobalMicMute(1)
SoundBeep beepMuteFreq, beepMuteDuration
NotifyMuted()
}

Media_Prev::
{ ; Triple press of action button
Media_Prev:: { ; Triple press of action button: Unmute all mics
NotifyUnmuted()
SetGlobalMicMute(0)
SoundBeep beepUnmuteFreq, beepUnmuteDuration
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions
SetGlobalMicMute(muteState)
{ ; Loop through all our audio capture devices and set mute state
if (muteState = 0)
{ ; We're trying to unmute so update status indicators
A_TrayMenu.Rename("3&", "Microphones are: Active")
A_TrayMenu.Check("&Unmute all microphones")
A_TrayMenu.Uncheck("&Mute all microphones")
A_IconTip := "Microphones active"
TraySetIcon(iconUnmutedFilename, iconUnmutedResource)
TrayTip "Microphones active", "Bose QC Mic Mute", 20
} else { ; We're trying to do something that's not unmute
muteState := 1
A_TrayMenu.Rename("3&", "Microphones are: Muted")
A_TrayMenu.Check("&Mute all microphones")
A_TrayMenu.Uncheck("&Unmute all microphones")
A_IconTip := "Microphones muted"
TraySetIcon(iconMutedFilename, iconMutedResource)
TrayTip "Microphones muted", "Bose QC Mic Mute", 20
}
loop
{ ; Loop through every sound device looking for a capture component
SetGlobalMicMute(muteState) { ; Loop through all our audio capture devices and set mute state
loop { ; Loop through every sound device looking for a capture component
try ; Collect the device name
deviceName := SoundGetName(, deviceIndex := A_Index)
catch ; No more devices left
break
loop
{ ; Loop through every component on this device
loop { ; Loop through every component on this device
try
componentName := SoundGetName(componentIndex := A_Index, deviceIndex)
catch ; No more components
break
if (componentName = "Capture")
{ ; We found a capture device, try and mute it
if (componentName = "Capture") { ; We found a capture device, try and mute it
try SoundSetMute(muteState, componentIndex, deviceIndex)
}
}
Expand All @@ -150,20 +158,17 @@ GetMicMute()
; status of the first device to respond to our querey. Return 0
; if no devices could be queried.
muteState := ""
loop
{ ; Loop through every sound device
loop { ; Loop through every sound device
try ; Collect the device name
deviceName := SoundGetName(, deviceIndex := A_Index)
catch ; No more devices
return 0
loop
{ ; Loop through every component on this device
loop { ; Loop through every component on this device
try
componentName := SoundGetName(componentIndex := A_Index, deviceIndex)
catch ; No more components for this device
break
if (componentName = "Capture")
{
if (componentName = "Capture") {
try muteState := SoundGetMute(componentIndex, deviceIndex)
if (muteState != "")
{
Expand All @@ -172,22 +177,74 @@ GetMicMute()
}
}
}
return 0
}

ToggleGlobalMicMute()
{ ; Toggle mute state on all devices
micMute := GetMicMute()
if (micMute = 0)
{
if (micMute = 0) {
SetGlobalMicMute(1)
SoundBeep beepMuteFreq, beepMuteDuration
} else
{
NotifyMuted()
} else {
NotifyUnmuted()
SetGlobalMicMute(0)
}
}

NotifyMuted()
{ ; Update UI and notify user that we are muting mics
global beepEnabled
global toastEnabled
MICMUTED := LoadPicture(A_ScriptName, "Icon2", &IconType)
TraySetIcon "HICON:" MICMUTED
A_TrayMenu.Rename("3&", "Microphones are: Muted")
A_TrayMenu.Check("&Mute all microphones")
A_TrayMenu.Uncheck("&Unmute all microphones")
A_IconTip := "Microphones muted"
if (toastEnabled = 1) {
TrayTip "Microphones muted", "Bose QC Mic Mute", 20
}
if (beepEnabled = 1) {
SoundBeep beepMuteFreq, beepMuteDuration
}
}

NotifyUnmuted() { ; Update UI and notify user that we are unmuting mics
global beepEnabled
global toastEnabled
global MICUNMUTED
MICUNMUTED := LoadPicture(A_ScriptName, "Icon1", &IconType)
TraySetIcon "HICON:" MICUNMUTED
A_TrayMenu.Rename("3&", "Microphones are: Active")
A_TrayMenu.Check("&Unmute all microphones")
A_TrayMenu.Uncheck("&Mute all microphones")
A_IconTip := "Microphones active"
if (toastEnabled = 1) {
TrayTip "Microphones active", "Bose QC Mic Mute", 20
}
if (beepEnabled = 1) {
SoundBeep beepUnmuteFreq, beepUnmuteDuration
}
}

ReadSavedSettings() { ; Read any saved settings from the registry and assign a default if nothing found
global beepEnabled := RegRead("HKEY_CURRENT_USER\Software\BoseQCMicMute", "beepEnabled", 1)
global toastEnabled := RegRead("HKEY_CURRENT_USER\Software\BoseQCMicMute", "toastEnabled", 1)
}

WriteSavedSettings() { ; Write settings to the registry
global beepEnabled
global toastEnabled
RegWrite beepEnabled, "REG_DWORD", "HKEY_CURRENT_USER\Software\BoseQCMicMute", "beepEnabled"
RegWrite toastEnabled, "REG_DWORD", "HKEY_CURRENT_USER\Software\BoseQCMicMute", "toastEnabled"
InstallKeybdHook(true, true)
}

ReHookHotkeys() { ; Re-register keyboard hook
InstallKeybdHook(true, true)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Menu handlers
MenuHandlerAbout(ItemName, ItemPos, MyMenu) {
Expand All @@ -200,14 +257,45 @@ MenuHandlerExit(ItemName, ItemPos, MyMenu) {

MenuHandlerMuteAll(ItemName, ItemPos, MyMenu) {
SetGlobalMicMute(1)
SoundBeep beepMuteFreq, beepMuteDuration
NotifyMuted()
InstallKeybdHook(true, true)
}

MenuHandlerUnMuteAll(ItemName, ItemPos, MyMenu) {
NotifyUnmuted()
SetGlobalMicMute(0)
SoundBeep beepUnmuteFreq, beepUnmuteDuration
InstallKeybdHook(true, true)
}

MenuHandlerMuteToggle(ItemName, ItemPos, MyMenu) {
ToggleGlobalMicMute
ToggleGlobalMicMute()
InstallKeybdHook(true, true)
}

MenuHandlerEnableToast(ItemName, ItemPos, MyMenu) {
A_TrayMenu.Check("Enable toast notification")
A_TrayMenu.Uncheck("Disable toast notification")
global toastEnabled := 1
WriteSavedSettings()
}

MenuHandlerDisableToast(ItemName, ItemPos, MyMenu) {
A_TrayMenu.Check("Disable toast notification")
A_TrayMenu.Uncheck("Enable toast notification")
global toastEnabled := 0
WriteSavedSettings()
}

MenuHandlerEnableBeep(ItemName, ItemPos, MyMenu) {
A_TrayMenu.Check("Disable audio notification")
A_TrayMenu.Uncheck("Enable audio notification")
global beepEnabled := 1
WriteSavedSettings()
}

MenuHandlerDisableBeep(ItemName, ItemPos, MyMenu) {
A_TrayMenu.Check("Disable audio notification")
A_TrayMenu.Uncheck("Enable audio notification")
global beepEnabled := 0
WriteSavedSettings()
}
File renamed without changes.
File renamed without changes.

0 comments on commit 7730cc8

Please sign in to comment.