This originally started as a tool to make up for the lack of a key-controlled volume control on my new laptop. I've also always been a little irritated by Windows' default volume control, so I took the opportunity to make myself happier there as well. Basically, this script adds hotkey controls for changing volume and mute as well as OSD for the hotkey actions. It also does what the standard Windows' volume control does--but with what is for me a less irritating dismissal model. Also, check out the system tray icon as you change volume level... it changes along with the volume setting (in three increments). Just like in the original, a right-click on the system tray icon lets you to launch the standard Windows Mixer if you need to get there.
I now disable the standard Windows system tray volume control and use this one exclusively. This version (.zip archive) is about 95% done. The archive includes the needed icon and sound resources. Before finalizing the script, I'd appreciate any constructive feedback. A big thank-you to script contributors (especially Rajat) for providing the code snippets that speeded up development.
Note that the first time you click on the system tray icon or select "Open" from the right-click popup menu, the popup volume control will be in the center of your screen. If you then move the window, its location will be saved. The OSD is not movable and will always be in the center of the user's screen. The icons are from the open source Tango project. The script was written and tested on WinXP-Home SP2 with AutoHotkey v1.0.44.07. I have no idea how will it will work in Win98, etc.
Here's the code:
; HotkeyVolume.ahk ; ; A hotkey volume controller... because the world just needed another one. ; Provides basic hotkey control of a system's sound level with on-screen (OSD) and system tray feedback. ; Designed to replace the standard Windows system tray volume control. ; Note: If the sound level is changed outside this script (e.g., using the standard Windows ; volume control), the system tray icon and tooltip will *not* automatically reflect the new settings. ; However, the OSD will correctly report the state. I don't know how to work around this behavior ; without polling--an approach that is awkward and too expensive for this type of script. ; ; The default location of the volume slider window that comes up when the user clicks the system tray icon ; or selects "Open" from the right-click popup menu is in the center of the user's screen. If the window is ; moved, the window's location will be restored after it is dismissed and even after the script is exited ; and restarted. The OSD is not movable and will always be in the center of the user's screen. ; ; This script uses bits and pieces taken from AHK community contributions, especially from Rajat! ; Icons are from the open source Tango project http://tango.freedesktop.org/Tango_Desktop_Project. ; ; (c) 2006 Mithat Konar ; v. 0.95(b) ; Wed Jul 12 04:00:00 EEST 2006 @83 /Internet Time/ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; TODO: Find a better project name. ; TODO: Better (and without potential licensing issue) "Ding" sound ; TODO: [next version] user-configurable hotkeys ; TODO: [next version] add WinLIRC support? ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #SingleInstance ignore ;----- system tray setup ----- ; set system tray icons global constants kUnmuteIcoLo := "rsc/audio-volume-low.ico" ; system tray low volume icon kUnmuteIcoMed := "rsc/audio-volume-medium.ico" ; system tray medium volume icon kUnmuteIcoHi := "rsc/audio-volume-high.ico" ; system tray high icon kMuteIco := "rsc/audio-volume-muted.ico" ; system tray muted icon ; setup system tray menu Menu, Tray, NoStandard Menu, Tray, Add, About..., About Menu, Tray, Add, Open mixer, OpenMixer Menu, Tray, Add, Open, Open Menu, Tray, Add Menu, Tray, Add, Up, VolUp Menu, Tray, Add, Down, VolDown Menu, Tray, Add, Mute, ToggleMute Menu, Tray, Add Menu, Tray, Add, Exit, Quit Menu, Tray, Click, 1 Menu, Tray, Default, Open ; set the tray icon, etc. gosub SetTray ; and the tray tooltip SoundGet, masterVolume masterVolume := Round(masterVolume) Menu, Tray, Tip , Volume [%masterVolume%`%] ;----- other global constants ----- ; OSD appearance and behavior kWindowColor := "111111" ;color of OSD slider background kBarColor := "44ee44" ;greenish color of OSD slider when audio is unmuted ;kBarColor := "87D200" ;another greenish color of OSD slider when audio is unmuted kMuteBarColor := "999999" ;grey color of OSD slider when audio is muted kBarHeight = 12 ;height of OSD indicator bar in pixels kBarWidth = 200 ;width of OSD indicator bar in pixels kUnmuted := "rsc/audio-volume-unmuted-48a.gif" ;OSD image of unmuted master audio kMuted := "rsc/audio-volume-muted-48a.gif" ;OSDimage of muted master audio kDing := "rsc/ding.wav" ; sound that plays when volume is set using GUI window kSliderTimeout := 500 ; time (msec) for OSD slider to go away after changing volume kMuteTimeout := 1000 ; time (msec) for OSD mute on/off icon to go away kNumVolumeSteps := 25 ; number of gradations in the OSD (hotkey controlled) volume control ; other OSD stuff kOSDVolName := "volOSD_095B" ; make this a window name guaranteed to not exist anywhere else kOSDMuteName := "muteOSD_095B" ; make this a window name guaranteed to not exist anywhere else ; GUI window stuff kVolumeWinTitle := "Volume" ; title of volume slider window kVolumeWinMute := "Mute" ; label for mute checkbox in volume slider window kGUIpanelWidth := 100 ; width of volume slider window kGUIsliderHeight := 100 ; height of actual volume slider ; About box stuff kAboutMsgBoxTitle := "About HotkeyVolume" kAboutStr := "HotkeyVolume`nv0.95b`n`nUp:`tAlt-UpArrow`nDown:`tAlt-DownArrow`nMute:`tAlt-M`n`n© 2006 Mithat Konar" ; ini file info kIniFilename = volume.ini ; name of ini file kXposName = xpos ; name of GUI window x position value kYposName = ypos ; name of GUI window y position value kWidthName = width ; name of GUI window width value kHeightName = height ; name of GUI window height value ;----- other globalVariables ----- ; nothing to see here... move along please... ;----- Hotkey definitions ----- !Up:: ;Alt+UpArrow turns master volume up gosub VolUp return !Down:: ;Alt+DownArrow turns master volume down gosub VolDown return !m:: ;Alt-M. mutes master sound gosub ToggleMute return ; These should be used only during debugging ;^!x::gosub Quit ; Ctrl-Alt-X exits script ;^!r::Reload ; Assign Ctrl-Alt-R restarts the script. ;----- Subroutines -----; VolUp: ; Turns volume up and updates OSD and system tray. gosub RemoveVolumeWindow incrementVol("up", kNumVolumeSteps) gosub ShowVolume return VolDown: ; Turns volume down and updates OSD and system tray. gosub RemoveVolumeWindow incrementVol("down", kNumVolumeSteps) gosub ShowVolume return ToggleMute: ; Toggles mute setting and updates OSD and system tray icon. gosub RemoveVolumeWindow SoundSet, +1, , mute ;toggle the master mute gosub ShowMute return ToggleMuteNoOSD: ; Toggles mute setting and updates system tray icon without updating OSD. SoundSet, +1, , mute ;toggle the master mute gosub SetTray return ShowVolume: ; Displays OSD volume indicator and updates system tray tooltip SoundGet, masterVolume masterVolume := Round(masterVolume) Menu, Tray, Tip , Volume [%masterVolume%`%] IfWinNotExist, %kOSDVolName% { SoundGet, masterMute, , mute if (masterMute = "On") barColor := kMuteBarColor else barColor := kBarColor Progress, b r0-100 cw%kWindowColor% cb%barColor% w%kBarWidth% zh%kBarHeight% zx0 zy0,,, %kOSDVolName% ; Set the position of the bar to %masterVolume%. } IfWinExist, %kOSDMuteName% ; if a mute indicator is on screen, remove it SplashImage, Off Progress, %masterVolume% ; set the position of the bar to %master_volume%. SetTimer, RemoveProgress, %kSliderTimeout% gosub SetTray return ShowMute: ; Displays OSD mute state indicator. gosub SetTray SoundGet, masterMute, , mute IfWinExist, %kOSDVolName% ; if a volume slider is on screen, remove it Progress, Off if (masterMute = "On") muteStateImage := kMuted else muteStateImage := kUnMuted SplashImage, %muteStateImage%, b,,, %kOSDMuteName% SetTimer, RemoveSplashImage, %kMuteTimeout% return SetTray: ; Updates the system tray icon. SoundGet, masterMute, , mute if (masterMute = "On") { Menu, Tray, Check, Mute trayImage := kMuteIco } else { Menu, Tray, Uncheck, Mute SoundGet, masterVolume ; show a different icon depending on volume setting if (masterVolume < (100.0/3.0)) trayImage := kUnmuteIcoLo else if (masterVolume < (2.0*100.0/3.0)) trayImage := kUnmuteIcoMed else trayImage := kUnmuteIcoHi } Menu, Tray, Icon, %trayImage% return RemoveProgress: ; Removes a progress bar. SetTimer, RemoveProgress, Off ;turn the timer off Progress, Off ;turn the progress bar off return RemoveSplashImage: ; Removes splash image SetTimer, RemoveSplashImage, Off ;turn the timer off SplashImage, Off ;turn the splashimage off return RemoveVolumeWindow: ; Removes the GUI volume window if it exists if volGUIWinExist() WinClose, %kVolumeWinTitle% ahk_class AutoHotkeyGUI, %kVolumeWinMute% return About: ; Displays or activates an About box. ; The "if aboutBoxExist()" test below is needed to keep multiple instances of the About MsgBox from appearing when ; the user clicks on the system tray repeatedly. The MsgBox is declared as modal, but in spite of that AHK ; makes multiple MsgBoxes. (Is this an AHK bug? Is there a better way to do this?) gosub RemoveVolumeWindow if aboutBoxExist() WinActivate, %kAboutMsgBoxTitle% ahk_class #32770, %kAboutStr% else MsgBox, 8256, %kAboutMsgBoxTitle%, %kAboutStr% return Open: ; Opens a GUI window volume control if it doesn't already exist. If the GUI window volume control already ; exists, activates it. gosub SetTray ; update parameters in case they have been changed outside this script if volGUIWinExist() WinActivate, %kVolumeWinTitle% ahk_class AutoHotkeyGUI, %kVolumeWinMute% else { Gui, +AlwaysOnTop -MaximizeBox -MinimizeBox +Owner ; +Owner avoids a taskbar button. ; setup volume slider SoundGet, GUImasterVolume Gui, Add, Slider, vertical invert NoTicks center h%kGUIsliderHeight% vGUImasterVolume gGUIUpdateVol, %GUImasterVolume% ; jump thru hoops to center the slider... GuiControlGet, GUImasterVolume, Pos ; the width is in GUImasterVolumeW sliderXpos := (kGUIpanelWidth - GUImasterVolumeW)/2 ; center the slider GuiControl, Move, GUImasterVolume, x%sliderXpos% ; setup mute checkbox SoundGet, masterMute, , mute if (masterMute = "On") muteState := 1 else muteState := 0 Gui, Add, Checkbox, Checked%muteState% gToggleMuteNoOSD, %kVolumeWinMute% ; show the volume control ; get x, y location information from ini file IniRead, xpos, %kIniFilename%, %kVolumeWinTitle%, %kXposName% , center IniRead, ypos, %kIniFilename%, %kVolumeWinTitle%, %kYposName% , center Gui, Show, x%xpos% y%ypos% w%kGUIpanelWidth%, %kVolumeWinTitle% } return ; Close, cancel, escape all destroy the GUI window volume control; window location and size are also recorded. GuiEscape: GuiClose: ButtonCancel: WinGetPos, x, y, width, height, %kVolumeWinTitle% IniWrite, %x%, %kIniFilename%, %kVolumeWinTitle%, %kXposName% IniWrite, %y%, %kIniFilename%, %kVolumeWinTitle%, %kYposName% IniWrite, %width%, %kIniFilename%, %kVolumeWinTitle%, width IniWrite, %height%, %kIniFilename%, %kVolumeWinTitle%, height Gui, Destroy return GUIUpdateVol: ; Used with the GUI volume window. Sets the system volume level based on position of the GUI volume window's slider ; and updates system tray tooltip. SoundSet, %GUImasterVolume% ; set the volume level gosub SetTray GUImasterVolume := Round(GUImasterVolume) Menu, Tray, Tip , Volume [%GUImasterVolume%`%] SoundPlay, %kDing% return OpenMixer: ; Opens the standard Windows mixer (for more advanced volume control). gosub RemoveVolumeWindow Run, sndvol32.exe return Quit: ; Er.... does the obvious. ExitApp return ;----- Functions ----- incrementVol(direction, numVolumeSteps) ; If direction = "up", the volume level is incremented by 100/numVolumeSteps; otherwise volume is ; decremented by the same amount. { SoundGet, masterVolume ;get existing volume level (0 to 100) masterVolume := masterVolume/100 * numVolumeSteps ;scale to numVolumeSteps (0 to kNumVolumeSteps) masterVolume := Round(masterVolume) ;quantize if(direction = "up" or direction = "UP") masterVolume++ ;add 1 else masterVolume-- ;subtract 1 masterVolume := Round(masterVolume/numVolumeSteps * 100) ;rescale to 0 to 100 SoundSet, %masterVolume% ;set the volume level } volGUIWinExist() ; Returns 1 if the GUI volume window exists, 0 otherwise. ; I made this a function in case a better way to positively identify the window is found. { global kVolumeWinTitle global kVolumeWinMute IfWinExist, %kVolumeWinTitle% ahk_class AutoHotkeyGUI, %kVolumeWinMute% return 1 return 0 } aboutBoxExist() ; Returns 1 if the "About" MsgBox exists, 0 otherwise. ; I made this a function in case a better way to positively identify the box is found. { global kAboutMsgBoxTitle global kAboutStr IfWinExist, %kAboutMsgBoxTitle% ahk_class #32770, %kAboutStr% return 1 return 0 }