├── .gitignore ├── README.md ├── SoundSource.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── package-soundsource.sh ├── res ├── AppIcon.icns ├── English.lproj │ ├── AboutBox.xib │ ├── MainMenu.xib │ └── ReadMe.rtfd │ │ ├── Picture 1.png │ │ ├── Picture2.png │ │ ├── ReadMeHeader.png │ │ ├── SSMenu.png │ │ ├── SoundSourceHeader.png │ │ ├── TXT.rtf │ │ └── ammo.jpg ├── Info.plist ├── menuIconAlt.pdf └── menuIconBlack.pdf └── src ├── SSAboutBox.h ├── SSAboutBox.m ├── SSAppController.h ├── SSAppController.m ├── SSAudioDeviceCenter.h ├── SSAudioDeviceCenter.m ├── SSLegacy.h ├── SSLegacy.m ├── SSMenuVolumeSliderView.h ├── SSMenuVolumeSliderView.m ├── SSPreferences.h ├── SSPreferences.m ├── SoundSource_Prefix.pch └── soundsource ├── main.m ├── soundsource-Prefix.pch └── soundsource.1 /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | xcuserdata 3 | .DS_Store 4 | .idea 5 | *.pbxuser 6 | *.mode?v3 7 | *~ 8 | *~.nib 9 | #* 10 | soundsource-* 11 | soundsource 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SoundSource 2 | =========== 3 | 4 | This project contains a version of Rogue Amoeba's SoundSource 2.5 5 | with an added command line tool for switching audio devices 6 | and setting volume. 7 | 8 | If you want a graphical version of SoundSource, you can get one from 9 | [Rogue Amoeba](https://rogueamoeba.com/soundsource/). Current 10 | versions of Rogue Amoeba's SoundSource are commercial products, not 11 | open source. 12 | 13 | The command line tool has no man page yet, but here's a simple 14 | example of how to use it: 15 | 16 | % soundsource -h 17 | usage: soundsource [-ios] [device] 18 | or: soundsource [-IOS] volume 19 | or: soundsource [-Mm] 20 | -i display selected audio input device 21 | -o display selected audio output device 22 | -s display output device used for alert sounds, sound effects 23 | -i device set selected audio input device 24 | -o device set selected audio output device 25 | -s device set output device used for alert sounds, sound effects 26 | -I display selected audio input device's volume 27 | -O display selected audio output device's volume 28 | -S display alert sounds/sound effects volume 29 | -I volume set selected audio input device's volume 30 | -O volume set selected audio output device's volume 31 | -S volume set alert sounds/sound effects volume 32 | -M mute selected audio output device 33 | -m unmute selected audio output device 34 | With no arguments, displays available/selected (*) devices and volumes. 35 | % soundsource 36 | Output (volume 0.001): 37 | AirPlay: Furrball 38 | AirPlay: Furrball II 39 | * Andrea PureAudio USB-SA Headset 40 | BW900 HS 41 | Internal Speakers 42 | Soundflower (16ch) 43 | Soundflower (2ch) 44 | ZoomSwitch USB Adapter 45 | Input (volume 0.926): 46 | Andrea PureAudio USB-SA Headset: External Line Connector 47 | Andrea PureAudio USB-SA Headset: External SPDIF Interface 48 | * Andrea PureAudio USB-SA Headset: Microphone 49 | BW900 HS 50 | Line In 51 | Soundflower (16ch) 52 | Soundflower (2ch) 53 | ZoomSwitch USB Adapter 54 | System (volume 0.989): 55 | * Internal Speakers 56 | % soundsource -i 57 | Andrea PureAudio USB-SA Headset: Microphone 58 | % soundsource -o 59 | Andrea PureAudio USB-SA Headset 60 | % soundsource -s 61 | Internal Speakers 62 | % soundsource -o 'Internal Speakers' 63 | % soundsource -o 64 | Internal Speakers 65 | % soundsource -O 0.8 66 | % soundsource -O 67 | 0.802 68 | 69 | A few examples of using `soundsource` in scripts are 70 | [on my blog](https://njr.sabi.net/2014/06/21/soundsource-a-few-examples/). 71 | -------------------------------------------------------------------------------- /SoundSource.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 781000A8104382020069B86A /* ReadMe.rtfd in Resources */ = {isa = PBXBuildFile; fileRef = 781000A5104382020069B86A /* ReadMe.rtfd */; }; 11 | 781000EC104383AD0069B86A /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 781000EB104383AD0069B86A /* AppIcon.icns */; }; 12 | 7810FF2910437E490069B86A /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7810FF2310437E490069B86A /* MainMenu.xib */; }; 13 | 7810FF3510437E8C0069B86A /* SSAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF3410437E8C0069B86A /* SSAppController.m */; }; 14 | 7810FF5610437F220069B86A /* SSAudioDeviceCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF4F10437F220069B86A /* SSAudioDeviceCenter.m */; }; 15 | 7810FF5710437F220069B86A /* SSAboutBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF5110437F220069B86A /* SSAboutBox.m */; }; 16 | 7810FF5810437F220069B86A /* SSPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF5210437F220069B86A /* SSPreferences.m */; }; 17 | 7810FF5910437F220069B86A /* SSMenuVolumeSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF5310437F220069B86A /* SSMenuVolumeSliderView.m */; }; 18 | 7810FF5E10437F4F0069B86A /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7810FF5D10437F4F0069B86A /* CoreAudio.framework */; }; 19 | 7810FF6E10437F880069B86A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7810FF6D10437F880069B86A /* Carbon.framework */; }; 20 | 78A76818104702F500349A68 /* SSLegacy.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A76817104702F500349A68 /* SSLegacy.m */; }; 21 | 78A768771047133600349A68 /* menuIconAlt.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 78A768751047133600349A68 /* menuIconAlt.pdf */; }; 22 | 78A768781047133600349A68 /* menuIconBlack.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 78A768761047133600349A68 /* menuIconBlack.pdf */; }; 23 | 78AB6D39104499570011CE3D /* AboutBox.xib in Resources */ = {isa = PBXBuildFile; fileRef = 78AB6D37104499570011CE3D /* AboutBox.xib */; }; 24 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 25 | E15B5F24223EF4E900975AE7 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E15B5F23223EF4E900975AE7 /* AudioToolbox.framework */; }; 26 | E1C8B78416FA8FF300CCB05B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E1C8B78316FA8FF300CCB05B /* main.m */; }; 27 | E1C8B78816FA8FF300CCB05B /* soundsource.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = E1C8B78716FA8FF300CCB05B /* soundsource.1 */; }; 28 | E1C8B78C16FA919E00CCB05B /* SSAudioDeviceCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7810FF4F10437F220069B86A /* SSAudioDeviceCenter.m */; }; 29 | E1C8B78D16FA91BB00CCB05B /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7810FF5D10437F4F0069B86A /* CoreAudio.framework */; }; 30 | E1C8B78E16FA91DF00CCB05B /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7810FF6D10437F880069B86A /* Carbon.framework */; }; 31 | E1C8B78F16FA91E900CCB05B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXCopyFilesBuildPhase section */ 35 | E1C8B77D16FA8FF300CCB05B /* CopyFiles */ = { 36 | isa = PBXCopyFilesBuildPhase; 37 | buildActionMask = 2147483647; 38 | dstPath = ""; 39 | dstSubfolderSpec = 16; 40 | files = ( 41 | E1C8B78816FA8FF300CCB05B /* soundsource.1 in CopyFiles */, 42 | ); 43 | runOnlyForDeploymentPostprocessing = 1; 44 | }; 45 | /* End PBXCopyFilesBuildPhase section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 49 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 50 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 51 | 781000A6104382020069B86A /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.rtfd; name = English; path = English.lproj/ReadMe.rtfd; sourceTree = ""; }; 52 | 781000EB104383AD0069B86A /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = ""; }; 53 | 7810FF2410437E490069B86A /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 54 | 7810FF2510437E490069B86A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 7810FF2810437E490069B86A /* SoundSource_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundSource_Prefix.pch; sourceTree = ""; }; 56 | 7810FF3310437E8C0069B86A /* SSAppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSAppController.h; sourceTree = ""; }; 57 | 7810FF3410437E8C0069B86A /* SSAppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSAppController.m; sourceTree = ""; }; 58 | 7810FF4E10437F220069B86A /* SSAudioDeviceCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSAudioDeviceCenter.h; sourceTree = ""; }; 59 | 7810FF4F10437F220069B86A /* SSAudioDeviceCenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSAudioDeviceCenter.m; sourceTree = ""; }; 60 | 7810FF5010437F220069B86A /* SSMenuVolumeSliderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSMenuVolumeSliderView.h; sourceTree = ""; }; 61 | 7810FF5110437F220069B86A /* SSAboutBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSAboutBox.m; sourceTree = ""; }; 62 | 7810FF5210437F220069B86A /* SSPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSPreferences.m; sourceTree = ""; }; 63 | 7810FF5310437F220069B86A /* SSMenuVolumeSliderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSMenuVolumeSliderView.m; sourceTree = ""; }; 64 | 7810FF5410437F220069B86A /* SSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSPreferences.h; sourceTree = ""; }; 65 | 7810FF5510437F220069B86A /* SSAboutBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSAboutBox.h; sourceTree = ""; }; 66 | 7810FF5D10437F4F0069B86A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = /System/Library/Frameworks/CoreAudio.framework; sourceTree = ""; }; 67 | 7810FF6D10437F880069B86A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; 68 | 78A76816104702F500349A68 /* SSLegacy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSLegacy.h; sourceTree = ""; }; 69 | 78A76817104702F500349A68 /* SSLegacy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSLegacy.m; sourceTree = ""; }; 70 | 78A768751047133600349A68 /* menuIconAlt.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = menuIconAlt.pdf; sourceTree = ""; }; 71 | 78A768761047133600349A68 /* menuIconBlack.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = menuIconBlack.pdf; sourceTree = ""; }; 72 | 78AB6D38104499570011CE3D /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/AboutBox.xib; sourceTree = ""; }; 73 | 8D1107320486CEB800E47090 /* SoundSource.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SoundSource.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | E15B5F23223EF4E900975AE7 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 75 | E15B5F25223EF4EC00975AE7 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 76 | E1C8B77F16FA8FF300CCB05B /* soundsource */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = soundsource; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | E1C8B78316FA8FF300CCB05B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 78 | E1C8B78616FA8FF300CCB05B /* soundsource-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "soundsource-Prefix.pch"; sourceTree = ""; }; 79 | E1C8B78716FA8FF300CCB05B /* soundsource.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = soundsource.1; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 88 | 7810FF5E10437F4F0069B86A /* CoreAudio.framework in Frameworks */, 89 | 7810FF6E10437F880069B86A /* Carbon.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | E1C8B77C16FA8FF300CCB05B /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | E15B5F24223EF4E900975AE7 /* AudioToolbox.framework in Frameworks */, 98 | E1C8B78D16FA91BB00CCB05B /* CoreAudio.framework in Frameworks */, 99 | E1C8B78E16FA91DF00CCB05B /* Carbon.framework in Frameworks */, 100 | E1C8B78F16FA91E900CCB05B /* Foundation.framework in Frameworks */, 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXFrameworksBuildPhase section */ 105 | 106 | /* Begin PBXGroup section */ 107 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 111 | 7810FF6D10437F880069B86A /* Carbon.framework */, 112 | 7810FF5D10437F4F0069B86A /* CoreAudio.framework */, 113 | E15B5F25223EF4EC00975AE7 /* AudioToolbox.framework */, 114 | ); 115 | name = "Linked Frameworks"; 116 | sourceTree = ""; 117 | }; 118 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 122 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 123 | ); 124 | name = "Other Frameworks"; 125 | sourceTree = ""; 126 | }; 127 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8D1107320486CEB800E47090 /* SoundSource.app */, 131 | E1C8B77F16FA8FF300CCB05B /* soundsource */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | 29B97314FDCFA39411CA2CEA /* SoundSource */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 7810FF2610437E490069B86A /* src */, 140 | 7810FF2210437E490069B86A /* res */, 141 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 142 | 19C28FACFE9D520D11CA2CBB /* Products */, 143 | ); 144 | name = SoundSource; 145 | sourceTree = ""; 146 | }; 147 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | E15B5F23223EF4E900975AE7 /* AudioToolbox.framework */, 151 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 152 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 153 | ); 154 | name = Frameworks; 155 | sourceTree = ""; 156 | }; 157 | 7810FF2210437E490069B86A /* res */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 7810FF2510437E490069B86A /* Info.plist */, 161 | 781000EB104383AD0069B86A /* AppIcon.icns */, 162 | 78A768751047133600349A68 /* menuIconAlt.pdf */, 163 | 78A768761047133600349A68 /* menuIconBlack.pdf */, 164 | 781000A5104382020069B86A /* ReadMe.rtfd */, 165 | 7810FF2310437E490069B86A /* MainMenu.xib */, 166 | 78AB6D37104499570011CE3D /* AboutBox.xib */, 167 | ); 168 | path = res; 169 | sourceTree = ""; 170 | }; 171 | 7810FF2610437E490069B86A /* src */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | E1C8B78216FA8FF300CCB05B /* soundsource */, 175 | 7810FF2810437E490069B86A /* SoundSource_Prefix.pch */, 176 | 7810FF3310437E8C0069B86A /* SSAppController.h */, 177 | 7810FF3410437E8C0069B86A /* SSAppController.m */, 178 | 7810FF5410437F220069B86A /* SSPreferences.h */, 179 | 7810FF5210437F220069B86A /* SSPreferences.m */, 180 | 7810FF5510437F220069B86A /* SSAboutBox.h */, 181 | 7810FF5110437F220069B86A /* SSAboutBox.m */, 182 | 7810FF4E10437F220069B86A /* SSAudioDeviceCenter.h */, 183 | 7810FF4F10437F220069B86A /* SSAudioDeviceCenter.m */, 184 | 7810FF5010437F220069B86A /* SSMenuVolumeSliderView.h */, 185 | 7810FF5310437F220069B86A /* SSMenuVolumeSliderView.m */, 186 | 78A76816104702F500349A68 /* SSLegacy.h */, 187 | 78A76817104702F500349A68 /* SSLegacy.m */, 188 | ); 189 | path = src; 190 | sourceTree = ""; 191 | }; 192 | E1C8B78216FA8FF300CCB05B /* soundsource */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | E1C8B78316FA8FF300CCB05B /* main.m */, 196 | E1C8B78716FA8FF300CCB05B /* soundsource.1 */, 197 | E1C8B78516FA8FF300CCB05B /* Supporting Files */, 198 | ); 199 | path = soundsource; 200 | sourceTree = ""; 201 | }; 202 | E1C8B78516FA8FF300CCB05B /* Supporting Files */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | E1C8B78616FA8FF300CCB05B /* soundsource-Prefix.pch */, 206 | ); 207 | name = "Supporting Files"; 208 | sourceTree = ""; 209 | }; 210 | /* End PBXGroup section */ 211 | 212 | /* Begin PBXNativeTarget section */ 213 | 8D1107260486CEB800E47090 /* SoundSource */ = { 214 | isa = PBXNativeTarget; 215 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SoundSource" */; 216 | buildPhases = ( 217 | 8D1107290486CEB800E47090 /* Resources */, 218 | 8D11072C0486CEB800E47090 /* Sources */, 219 | 8D11072E0486CEB800E47090 /* Frameworks */, 220 | ); 221 | buildRules = ( 222 | ); 223 | dependencies = ( 224 | ); 225 | name = SoundSource; 226 | productInstallPath = "$(HOME)/Applications"; 227 | productName = SoundSource; 228 | productReference = 8D1107320486CEB800E47090 /* SoundSource.app */; 229 | productType = "com.apple.product-type.application"; 230 | }; 231 | E1C8B77E16FA8FF300CCB05B /* soundsource */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = E1C8B78916FA8FF300CCB05B /* Build configuration list for PBXNativeTarget "soundsource" */; 234 | buildPhases = ( 235 | E1C8B77B16FA8FF300CCB05B /* Sources */, 236 | E1C8B77C16FA8FF300CCB05B /* Frameworks */, 237 | E1C8B77D16FA8FF300CCB05B /* CopyFiles */, 238 | ); 239 | buildRules = ( 240 | ); 241 | dependencies = ( 242 | ); 243 | name = soundsource; 244 | productName = soundsource; 245 | productReference = E1C8B77F16FA8FF300CCB05B /* soundsource */; 246 | productType = "com.apple.product-type.tool"; 247 | }; 248 | /* End PBXNativeTarget section */ 249 | 250 | /* Begin PBXProject section */ 251 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 252 | isa = PBXProject; 253 | attributes = { 254 | LastUpgradeCheck = 0600; 255 | }; 256 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SoundSource" */; 257 | compatibilityVersion = "Xcode 3.2"; 258 | developmentRegion = English; 259 | hasScannedForEncodings = 1; 260 | knownRegions = ( 261 | English, 262 | Japanese, 263 | French, 264 | German, 265 | ); 266 | mainGroup = 29B97314FDCFA39411CA2CEA /* SoundSource */; 267 | projectDirPath = ""; 268 | projectRoot = ""; 269 | targets = ( 270 | 8D1107260486CEB800E47090 /* SoundSource */, 271 | E1C8B77E16FA8FF300CCB05B /* soundsource */, 272 | ); 273 | }; 274 | /* End PBXProject section */ 275 | 276 | /* Begin PBXResourcesBuildPhase section */ 277 | 8D1107290486CEB800E47090 /* Resources */ = { 278 | isa = PBXResourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | 7810FF2910437E490069B86A /* MainMenu.xib in Resources */, 282 | 781000A8104382020069B86A /* ReadMe.rtfd in Resources */, 283 | 781000EC104383AD0069B86A /* AppIcon.icns in Resources */, 284 | 78AB6D39104499570011CE3D /* AboutBox.xib in Resources */, 285 | 78A768771047133600349A68 /* menuIconAlt.pdf in Resources */, 286 | 78A768781047133600349A68 /* menuIconBlack.pdf in Resources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXResourcesBuildPhase section */ 291 | 292 | /* Begin PBXSourcesBuildPhase section */ 293 | 8D11072C0486CEB800E47090 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | 7810FF3510437E8C0069B86A /* SSAppController.m in Sources */, 298 | 7810FF5610437F220069B86A /* SSAudioDeviceCenter.m in Sources */, 299 | 7810FF5710437F220069B86A /* SSAboutBox.m in Sources */, 300 | 7810FF5810437F220069B86A /* SSPreferences.m in Sources */, 301 | 7810FF5910437F220069B86A /* SSMenuVolumeSliderView.m in Sources */, 302 | 78A76818104702F500349A68 /* SSLegacy.m in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | E1C8B77B16FA8FF300CCB05B /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | E1C8B78416FA8FF300CCB05B /* main.m in Sources */, 311 | E1C8B78C16FA919E00CCB05B /* SSAudioDeviceCenter.m in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXSourcesBuildPhase section */ 316 | 317 | /* Begin PBXVariantGroup section */ 318 | 781000A5104382020069B86A /* ReadMe.rtfd */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 781000A6104382020069B86A /* English */, 322 | ); 323 | name = ReadMe.rtfd; 324 | sourceTree = ""; 325 | }; 326 | 7810FF2310437E490069B86A /* MainMenu.xib */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 7810FF2410437E490069B86A /* English */, 330 | ); 331 | name = MainMenu.xib; 332 | sourceTree = ""; 333 | }; 334 | 78AB6D37104499570011CE3D /* AboutBox.xib */ = { 335 | isa = PBXVariantGroup; 336 | children = ( 337 | 78AB6D38104499570011CE3D /* English */, 338 | ); 339 | name = AboutBox.xib; 340 | sourceTree = ""; 341 | }; 342 | /* End PBXVariantGroup section */ 343 | 344 | /* Begin XCBuildConfiguration section */ 345 | C01FCF4B08A954540054247B /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ALWAYS_SEARCH_USER_PATHS = NO; 349 | COMBINE_HIDPI_IMAGES = YES; 350 | COPY_PHASE_STRIP = NO; 351 | GCC_DYNAMIC_NO_PIC = NO; 352 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 353 | GCC_OPTIMIZATION_LEVEL = 0; 354 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 355 | GCC_PREFIX_HEADER = src/SoundSource_Prefix.pch; 356 | INFOPLIST_FILE = res/Info.plist; 357 | INSTALL_PATH = "$(HOME)/Applications"; 358 | PRODUCT_NAME = SoundSource; 359 | SDKROOT = macosx10.9; 360 | }; 361 | name = Debug; 362 | }; 363 | C01FCF4C08A954540054247B /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | COMBINE_HIDPI_IMAGES = YES; 368 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 369 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 370 | GCC_PREFIX_HEADER = src/SoundSource_Prefix.pch; 371 | INFOPLIST_FILE = res/Info.plist; 372 | INSTALL_PATH = "$(HOME)/Applications"; 373 | PRODUCT_NAME = SoundSource; 374 | SDKROOT = macosx10.9; 375 | }; 376 | name = Release; 377 | }; 378 | C01FCF4F08A954540054247B /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ARCHS = "$(ARCHS_STANDARD)"; 382 | CLANG_WARN_BOOL_CONVERSION = YES; 383 | CLANG_WARN_CONSTANT_CONVERSION = YES; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | CURRENT_PROJECT_VERSION = 3.1; 389 | GCC_C_LANGUAGE_STANDARD = c99; 390 | GCC_OPTIMIZATION_LEVEL = 0; 391 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | MACOSX_DEPLOYMENT_TARGET = 10.8; 399 | ONLY_ACTIVE_ARCH = YES; 400 | }; 401 | name = Debug; 402 | }; 403 | C01FCF5008A954540054247B /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ARCHS = "$(ARCHS_STANDARD)"; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_CONSTANT_CONVERSION = YES; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | CURRENT_PROJECT_VERSION = 3.1; 414 | GCC_C_LANGUAGE_STANDARD = c99; 415 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | MACOSX_DEPLOYMENT_TARGET = 10.8; 423 | }; 424 | name = Release; 425 | }; 426 | E1C8B78A16FA8FF300CCB05B /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_SEARCH_USER_PATHS = NO; 430 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 431 | CLANG_CXX_LIBRARY = "libc++"; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_EMPTY_BODY = YES; 434 | CLANG_WARN_ENUM_CONVERSION = YES; 435 | CLANG_WARN_INT_CONVERSION = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | COPY_PHASE_STRIP = NO; 438 | FRAMEWORK_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", 441 | ); 442 | GCC_C_LANGUAGE_STANDARD = gnu99; 443 | GCC_DYNAMIC_NO_PIC = NO; 444 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 445 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 446 | GCC_PREFIX_HEADER = "src/soundsource/soundsource-Prefix.pch"; 447 | GCC_PREPROCESSOR_DEFINITIONS = ( 448 | "DEBUG=1", 449 | "$(inherited)", 450 | ); 451 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 452 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 453 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SDKROOT = macosx; 456 | }; 457 | name = Debug; 458 | }; 459 | E1C8B78B16FA8FF300CCB05B /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 464 | CLANG_CXX_LIBRARY = "libc++"; 465 | CLANG_WARN_CONSTANT_CONVERSION = YES; 466 | CLANG_WARN_EMPTY_BODY = YES; 467 | CLANG_WARN_ENUM_CONVERSION = YES; 468 | CLANG_WARN_INT_CONVERSION = YES; 469 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 470 | COPY_PHASE_STRIP = YES; 471 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 472 | FRAMEWORK_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", 475 | ); 476 | GCC_C_LANGUAGE_STANDARD = gnu99; 477 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 478 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 479 | GCC_PREFIX_HEADER = "src/soundsource/soundsource-Prefix.pch"; 480 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 481 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | SDKROOT = macosx; 484 | }; 485 | name = Release; 486 | }; 487 | /* End XCBuildConfiguration section */ 488 | 489 | /* Begin XCConfigurationList section */ 490 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SoundSource" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | C01FCF4B08A954540054247B /* Debug */, 494 | C01FCF4C08A954540054247B /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SoundSource" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | C01FCF4F08A954540054247B /* Debug */, 503 | C01FCF5008A954540054247B /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | E1C8B78916FA8FF300CCB05B /* Build configuration list for PBXNativeTarget "soundsource" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | E1C8B78A16FA8FF300CCB05B /* Debug */, 512 | E1C8B78B16FA8FF300CCB05B /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | /* End XCConfigurationList section */ 518 | }; 519 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 520 | } 521 | -------------------------------------------------------------------------------- /SoundSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package-soundsource.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh -e 2 | 3 | set -x -v 4 | 5 | PACKAGEDIR="$PWD" 6 | PRODUCT="soundsource" 7 | 8 | find . -name \*~ -exec rm '{}' \; 9 | rm -rf build/ 10 | xcodebuild -target $PRODUCT -configuration Release DSTROOT=/ INSTALL_PATH="$PWD" install 11 | SetFile -c 'ttxt' -t 'TEXT' README.md 12 | 13 | sudo /usr/bin/install -d /usr/local/bin /usr/local/man/man1 14 | sudo /usr/bin/install $PRODUCT /usr/local/bin 15 | #sudo /usr/bin/install -m 644 $PRODUCT.1 /usr/local/man/man1 16 | chmod 755 $PRODUCT 17 | #chmod 644 $PRODUCT.1 18 | # man page is currently not usable, so don't install it 19 | VERSION=$(agvtool vers -terse) TARBALL="$PWD/$PRODUCT-$VERSION.tar.gz" 20 | rm -f ../$PRODUCT-$VERSION $TARBALL 21 | ln -s $PWD ../$PRODUCT-$VERSION 22 | cd .. 23 | /usr/bin/tar \ 24 | --exclude=.DS_Store --exclude=.git\* --exclude=.idea \ 25 | --exclude=build --exclude=\*.xcworkspace --exclude=xcuserdata \ 26 | --exclude=$PRODUCT-\*.tar.gz \ 27 | -zcLf $TARBALL $PRODUCT-$VERSION 28 | rm -f $PRODUCT-$VERSION 29 | scp $TARBALL osric:web/nriley/software/ 30 | -------------------------------------------------------------------------------- /res/AppIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/AppIcon.icns -------------------------------------------------------------------------------- /res/English.lproj/AboutBox.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1080 5 | 14B25 6 | 6254 7 | 1343.16 8 | 755.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 6254 12 | 13 | 14 | NSCustomObject 15 | NSScrollView 16 | NSScroller 17 | NSTextView 18 | NSView 19 | NSWindowTemplate 20 | 21 | 22 | com.apple.InterfaceBuilder.CocoaPlugin 23 | 24 | 25 | 26 | 27 | MCAboutBox 28 | 29 | 30 | FirstResponder 31 | 32 | 33 | NSApplication 34 | 35 | 36 | 11 37 | 2 38 | {{28, 412}, {506, 544}} 39 | 1886912512 40 | SoundSource: About 41 | NSWindow 42 | 43 | View 44 | 45 | 46 | {213, 107} 47 | 48 | 49 | 256 50 | 51 | 52 | 53 | 274 54 | 55 | 56 | 57 | 2322 58 | 59 | 60 | 61 | 2322 62 | 63 | Apple HTML pasteboard type 64 | Apple PDF pasteboard type 65 | Apple PICT pasteboard type 66 | CorePasteboardFlavorType 0x6D6F6F76 67 | NSColor pasteboard type 68 | NSFilenamesPboardType 69 | NSStringPboardType 70 | NeXT Encapsulated PostScript v1.2 pasteboard type 71 | NeXT RTFD pasteboard type 72 | NeXT Rich Text Format v1.0 pasteboard type 73 | NeXT TIFF v4.0 pasteboard type 74 | 75 | {504, 543} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 38 90 | 91 | 92 | 93 | 504 94 | 1 95 | 96 | 97 | 67111917 98 | 0 99 | 100 | 101 | 3 102 | MQA 103 | 104 | 105 | 106 | 6 107 | System 108 | selectedTextBackgroundColor 109 | 110 | 3 111 | MC42NjY2NjY2NjY3AA 112 | 113 | 114 | 115 | 6 116 | System 117 | selectedTextColor 118 | 119 | 3 120 | MAA 121 | 122 | 123 | 124 | 125 | 6 126 | System 127 | controlTextColor 128 | 129 | 130 | 131 | 132 | 1 133 | MCAwIDEAA 134 | 135 | 136 | {8, -8} 137 | 13 138 | 139 | 140 | 141 | 142 | 143 | 0 144 | 145 | 6 146 | {504, 10000000} 147 | 148 | 149 | 150 | {{1, 1}, {504, 543}} 151 | 152 | 153 | 154 | 155 | 156 | 157 | {4, 5} 158 | 159 | 79691776 160 | 161 | 162 | 163 | 164 | 165 | file:///Volumes/MaryYosemite/Users/nicholas/Applications/Xcode6-Beta5.app/Contents/SharedFrameworks/DVTKit.framework/Resources/DVTIbeamCursor.tiff 166 | 167 | 168 | 169 | 170 | 3 171 | MCAwAA 172 | 173 | 174 | 175 | 4 176 | YES 177 | 178 | 179 | 180 | 256 181 | {{489, 1}, {16, 543}} 182 | 183 | 184 | NO 185 | _doScroller: 186 | 187 | 188 | _doScroller: 189 | 1 190 | 191 | 192 | 193 | -2147483392 194 | {{-100, -100}, {87, 18}} 195 | 196 | 197 | NO 198 | _doScroller: 199 | 200 | 1 201 | 202 | _doScroller: 203 | 1 204 | 0.94565220000000005 205 | 206 | 207 | {506, 545} 208 | 209 | 210 | 211 | 133138 212 | 213 | 214 | 215 | 0.25 216 | 4 217 | 1 218 | 219 | 220 | {506, 544} 221 | 222 | 223 | 224 | {{0, 0}, {1680, 1027}} 225 | {213, 129} 226 | {10000000000000, 10000000000000} 227 | YES 228 | 229 | 230 | 231 | 232 | 233 | 234 | mReadMeText 235 | 236 | 237 | 238 | 16 239 | 240 | 241 | 242 | window 243 | 244 | 245 | 246 | 17 247 | 248 | 249 | 250 | 251 | 252 | 0 253 | 254 | 255 | 256 | 257 | 258 | -2 259 | 260 | 261 | File's Owner 262 | 263 | 264 | -1 265 | 266 | 267 | First Responder 268 | 269 | 270 | -3 271 | 272 | 273 | Application 274 | 275 | 276 | 5 277 | 278 | 279 | 280 | 281 | 282 | Window 283 | 284 | 285 | 6 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 15 304 | 305 | 306 | 307 | 308 | 19 309 | 310 | 311 | 312 | 313 | 20 314 | 315 | 316 | 317 | 318 | 319 | 320 | com.apple.InterfaceBuilder.CocoaPlugin 321 | com.apple.InterfaceBuilder.CocoaPlugin 322 | com.apple.InterfaceBuilder.CocoaPlugin 323 | com.apple.InterfaceBuilder.CocoaPlugin 324 | com.apple.InterfaceBuilder.CocoaPlugin 325 | com.apple.InterfaceBuilder.CocoaPlugin 326 | 327 | com.apple.InterfaceBuilder.CocoaPlugin 328 | 329 | com.apple.InterfaceBuilder.CocoaPlugin 330 | {{0, 451}, {506, 544}} 331 | com.apple.InterfaceBuilder.CocoaPlugin 332 | 333 | 334 | 335 | 336 | 337 | 20 338 | 339 | 340 | 0 341 | IBCocoaFramework 342 | NO 343 | 344 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 345 | 346 | 347 | YES 348 | 3 349 | 350 | 351 | -------------------------------------------------------------------------------- /res/English.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1080 5 | 14B25 6 | 6254 7 | 1343.16 8 | 755.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 6254 12 | 13 | 14 | NSCustomObject 15 | NSMenu 16 | NSMenuItem 17 | 18 | 19 | com.apple.InterfaceBuilder.CocoaPlugin 20 | 21 | 22 | 23 | 24 | NSApplication 25 | 26 | 27 | FirstResponder 28 | 29 | 30 | NSApplication 31 | 32 | 33 | AMainMenu 34 | 35 | 36 | 37 | SoundSource 38 | 39 | 1048576 40 | 2147483647 41 | 42 | NSImage 43 | NSMenuCheckmark 44 | 45 | 46 | NSImage 47 | NSMenuMixedState 48 | 49 | submenuAction: 50 | 51 | 52 | SoundSource 53 | 54 | 55 | 56 | About SoundSource 57 | 58 | 2147483647 59 | 60 | 61 | 62 | 63 | 64 | YES 65 | YES 66 | 67 | 68 | 1048576 69 | 2147483647 70 | 71 | 72 | 73 | 74 | 75 | Services 76 | 77 | 1048576 78 | 2147483647 79 | 80 | 81 | submenuAction: 82 | 83 | 84 | Services 85 | 86 | _NSServicesMenu 87 | 88 | 89 | 90 | 91 | YES 92 | YES 93 | 94 | 95 | 1048576 96 | 2147483647 97 | 98 | 99 | 100 | 101 | 102 | Hide SoundSource 103 | h 104 | 1048576 105 | 2147483647 106 | 107 | 108 | 109 | 110 | 111 | Hide Others 112 | h 113 | 1572864 114 | 2147483647 115 | 116 | 117 | 118 | 119 | 120 | Show All 121 | 122 | 1048576 123 | 2147483647 124 | 125 | 126 | 127 | 128 | 129 | YES 130 | YES 131 | 132 | 133 | 1048576 134 | 2147483647 135 | 136 | 137 | 138 | 139 | 140 | Quit SoundSource 141 | q 142 | 1048576 143 | 2147483647 144 | 145 | 146 | 147 | 148 | _NSAppleMenu 149 | 150 | 151 | 152 | 153 | Edit 154 | 155 | 1048576 156 | 2147483647 157 | 158 | 159 | submenuAction: 160 | 161 | 162 | Edit 163 | 164 | 165 | 166 | Undo 167 | z 168 | 1048576 169 | 2147483647 170 | 171 | 172 | 173 | 174 | 175 | Redo 176 | Z 177 | 1179648 178 | 2147483647 179 | 180 | 181 | 182 | 183 | 184 | YES 185 | YES 186 | 187 | 188 | 1048576 189 | 2147483647 190 | 191 | 192 | 193 | 194 | 195 | Cut 196 | x 197 | 1048576 198 | 2147483647 199 | 200 | 201 | 202 | 203 | 204 | Copy 205 | c 206 | 1048576 207 | 2147483647 208 | 209 | 210 | 211 | 212 | 213 | Paste 214 | v 215 | 1048576 216 | 2147483647 217 | 218 | 219 | 220 | 221 | 222 | Delete 223 | 224 | 1048576 225 | 2147483647 226 | 227 | 228 | 229 | 230 | 231 | Select All 232 | a 233 | 1048576 234 | 2147483647 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | Window 244 | 245 | 1048576 246 | 2147483647 247 | 248 | 249 | submenuAction: 250 | 251 | 252 | Window 253 | 254 | 255 | 256 | Close 257 | w 258 | 1048576 259 | 2147483647 260 | 261 | 262 | 263 | 264 | 265 | Minimize 266 | m 267 | 1048576 268 | 2147483647 269 | 270 | 271 | 272 | 273 | 274 | Zoom 275 | 276 | 1048576 277 | 2147483647 278 | 279 | 280 | 281 | 282 | 283 | YES 284 | YES 285 | 286 | 287 | 1048576 288 | 2147483647 289 | 290 | 291 | 292 | 293 | 294 | Bring All to Front 295 | 296 | 1048576 297 | 2147483647 298 | 299 | 300 | 301 | 302 | _NSWindowsMenu 303 | 304 | 305 | 306 | 307 | Help 308 | 309 | 1048576 310 | 2147483647 311 | 312 | 313 | submenuAction: 314 | 315 | 316 | Help 317 | 318 | 319 | 320 | SoundSource Help 321 | ? 322 | 1048576 323 | 2147483647 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | _NSMainMenu 332 | 333 | 334 | SSAppController 335 | 336 | 337 | 338 | 339 | 340 | 341 | terminate: 342 | 343 | 344 | 345 | 449 346 | 347 | 348 | 349 | orderFrontStandardAboutPanel: 350 | 351 | 352 | 353 | 142 354 | 355 | 356 | 357 | delegate 358 | 359 | 360 | 361 | 451 362 | 363 | 364 | 365 | arrangeInFront: 366 | 367 | 368 | 369 | 39 370 | 371 | 372 | 373 | undo: 374 | 375 | 376 | 377 | 223 378 | 379 | 380 | 381 | copy: 382 | 383 | 384 | 385 | 224 386 | 387 | 388 | 389 | paste: 390 | 391 | 392 | 393 | 226 394 | 395 | 396 | 397 | cut: 398 | 399 | 400 | 401 | 228 402 | 403 | 404 | 405 | redo: 406 | 407 | 408 | 409 | 231 410 | 411 | 412 | 413 | selectAll: 414 | 415 | 416 | 417 | 232 418 | 419 | 420 | 421 | delete: 422 | 423 | 424 | 425 | 235 426 | 427 | 428 | 429 | performZoom: 430 | 431 | 432 | 433 | 240 434 | 435 | 436 | 437 | showHelp: 438 | 439 | 440 | 441 | 360 442 | 443 | 444 | 445 | hide: 446 | 447 | 448 | 449 | 367 450 | 451 | 452 | 453 | hideOtherApplications: 454 | 455 | 456 | 457 | 368 458 | 459 | 460 | 461 | unhideAllApplications: 462 | 463 | 464 | 465 | 370 466 | 467 | 468 | 469 | performClose: 470 | 471 | 472 | 473 | 454 474 | 475 | 476 | 477 | 478 | 479 | 0 480 | 481 | 482 | 483 | 484 | 485 | -2 486 | 487 | 488 | File's Owner 489 | 490 | 491 | -1 492 | 493 | 494 | First Responder 495 | 496 | 497 | -3 498 | 499 | 500 | Application 501 | 502 | 503 | 29 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | MainMenu 513 | 514 | 515 | 19 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 56 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 103 532 | 533 | 534 | 535 | 536 | 537 | 1 538 | 539 | 540 | 217 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 205 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 202 564 | 565 | 566 | 567 | 568 | 198 569 | 570 | 571 | 572 | 573 | 207 574 | 575 | 576 | 577 | 578 | 199 579 | 580 | 581 | 582 | 583 | 203 584 | 585 | 586 | 587 | 588 | 197 589 | 590 | 591 | 592 | 593 | 206 594 | 595 | 596 | 597 | 598 | 215 599 | 600 | 601 | 602 | 603 | 106 604 | 605 | 606 | 607 | 608 | 609 | 2 610 | 611 | 612 | 111 613 | 614 | 615 | 616 | 617 | 57 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 58 634 | 635 | 636 | 637 | 638 | 134 639 | 640 | 641 | 642 | 643 | 150 644 | 645 | 646 | 647 | 648 | 136 649 | 650 | 651 | 1111 652 | 653 | 654 | 144 655 | 656 | 657 | 658 | 659 | 236 660 | 661 | 662 | 663 | 664 | 131 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 149 673 | 674 | 675 | 676 | 677 | 145 678 | 679 | 680 | 681 | 682 | 130 683 | 684 | 685 | 686 | 687 | 24 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 92 700 | 701 | 702 | 703 | 704 | 5 705 | 706 | 707 | 708 | 709 | 239 710 | 711 | 712 | 713 | 714 | 23 715 | 716 | 717 | 718 | 719 | 450 720 | 721 | 722 | 723 | 724 | 452 725 | 726 | 727 | 728 | 729 | 730 | 731 | com.apple.InterfaceBuilder.CocoaPlugin 732 | com.apple.InterfaceBuilder.CocoaPlugin 733 | com.apple.InterfaceBuilder.CocoaPlugin 734 | com.apple.InterfaceBuilder.CocoaPlugin 735 | com.apple.InterfaceBuilder.CocoaPlugin 736 | com.apple.InterfaceBuilder.CocoaPlugin 737 | com.apple.InterfaceBuilder.CocoaPlugin 738 | com.apple.InterfaceBuilder.CocoaPlugin 739 | com.apple.InterfaceBuilder.CocoaPlugin 740 | com.apple.InterfaceBuilder.CocoaPlugin 741 | com.apple.InterfaceBuilder.CocoaPlugin 742 | com.apple.InterfaceBuilder.CocoaPlugin 743 | com.apple.InterfaceBuilder.CocoaPlugin 744 | com.apple.InterfaceBuilder.CocoaPlugin 745 | com.apple.InterfaceBuilder.CocoaPlugin 746 | com.apple.InterfaceBuilder.CocoaPlugin 747 | com.apple.InterfaceBuilder.CocoaPlugin 748 | com.apple.InterfaceBuilder.CocoaPlugin 749 | com.apple.InterfaceBuilder.CocoaPlugin 750 | com.apple.InterfaceBuilder.CocoaPlugin 751 | com.apple.InterfaceBuilder.CocoaPlugin 752 | com.apple.InterfaceBuilder.CocoaPlugin 753 | com.apple.InterfaceBuilder.CocoaPlugin 754 | com.apple.InterfaceBuilder.CocoaPlugin 755 | com.apple.InterfaceBuilder.CocoaPlugin 756 | com.apple.InterfaceBuilder.CocoaPlugin 757 | com.apple.InterfaceBuilder.CocoaPlugin 758 | com.apple.InterfaceBuilder.CocoaPlugin 759 | com.apple.InterfaceBuilder.CocoaPlugin 760 | com.apple.InterfaceBuilder.CocoaPlugin 761 | com.apple.InterfaceBuilder.CocoaPlugin 762 | com.apple.InterfaceBuilder.CocoaPlugin 763 | com.apple.InterfaceBuilder.CocoaPlugin 764 | com.apple.InterfaceBuilder.CocoaPlugin 765 | com.apple.InterfaceBuilder.CocoaPlugin 766 | com.apple.InterfaceBuilder.CocoaPlugin 767 | com.apple.InterfaceBuilder.CocoaPlugin 768 | 769 | 770 | 771 | 772 | 773 | 454 774 | 775 | 776 | 777 | 778 | SSAppController 779 | NSObject 780 | 781 | IBProjectSource 782 | ../src/SSAppController.h 783 | 784 | 785 | 786 | SSAppController 787 | 788 | id 789 | id 790 | id 791 | id 792 | id 793 | id 794 | id 795 | id 796 | 797 | 798 | 799 | hitAudioFollowsJack: 800 | id 801 | 802 | 803 | hitOpenAMS: 804 | id 805 | 806 | 807 | hitOpenAbout: 808 | id 809 | 810 | 811 | hitOpenAtLogin: 812 | id 813 | 814 | 815 | hitOpenSoundPrefs: 816 | id 817 | 818 | 819 | hitSelectInputDevice: 820 | id 821 | 822 | 823 | hitSelectOutputDevice: 824 | id 825 | 826 | 827 | hitSelectSystemDevice: 828 | id 829 | 830 | 831 | 832 | IBProjectSource 833 | ../src/SSAppController.m 834 | 835 | 836 | 837 | 838 | 0 839 | IBCocoaFramework 840 | NO 841 | 842 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 843 | 844 | 845 | YES 846 | 3 847 | 848 | {12, 12} 849 | {10, 2} 850 | 851 | 852 | 853 | -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/Picture 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/Picture 1.png -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/Picture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/Picture2.png -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/ReadMeHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/ReadMeHeader.png -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/SSMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/SSMenu.png -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/SoundSourceHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/SoundSourceHeader.png -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/TXT.rtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/TXT.rtf -------------------------------------------------------------------------------- /res/English.lproj/ReadMe.rtfd/ammo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/English.lproj/ReadMe.rtfd/ammo.jpg -------------------------------------------------------------------------------- /res/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | AppIcon.icns 11 | CFBundleIdentifier 12 | com.rogueamoeba.soundsource 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PRODUCT_VERSION} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | LSUIElement 28 | 29 | LSMinimumSystemVersion 30 | ${MACOSX_DEPLOYMENT_TARGET} 31 | 32 | 33 | -------------------------------------------------------------------------------- /res/menuIconAlt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/menuIconAlt.pdf -------------------------------------------------------------------------------- /res/menuIconBlack.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nriley/SoundSource/e26e8d12b7529c83b89d56f4a3a1776637d7548f/res/menuIconBlack.pdf -------------------------------------------------------------------------------- /src/SSAboutBox.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSAboutBox.h 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 1/4/05. 6 | // Copyright 2005 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface SSAboutBox : NSWindowController 13 | { 14 | IBOutlet NSTextView* mReadMeText; 15 | } 16 | 17 | + (SSAboutBox*)sharedAboutBox; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /src/SSAboutBox.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSAboutBox.m 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 1/4/05. 6 | // Copyright 2005 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSAboutBox.h" 10 | 11 | 12 | @implementation SSAboutBox 13 | 14 | static SSAboutBox* _sharedAboutBox = nil; 15 | 16 | + (SSAboutBox*)sharedAboutBox 17 | { 18 | if( _sharedAboutBox == nil ) 19 | _sharedAboutBox = [[self alloc] init]; 20 | return _sharedAboutBox; 21 | } 22 | 23 | - (id)init 24 | { 25 | return [self initWithWindowNibName: @"AboutBox"]; 26 | } 27 | 28 | - (void)windowDidLoad 29 | { 30 | [super windowDidLoad]; 31 | [[self window] center]; 32 | 33 | NSBundle* bundle = [NSBundle bundleForClass: [self class]]; 34 | NSString* path = [bundle pathForResource: @"ReadMe" ofType: @"rtfd"]; 35 | 36 | if( path ) 37 | [mReadMeText readRTFDFromFile: path]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /src/SSAppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSAppController.h 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/24/09. 6 | // Copyright 2009 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class SSPreferences; 12 | @class SSAudioDeviceCenter; 13 | 14 | @interface SSAppController : NSObject 15 | { 16 | NSStatusItem* _statusItem; 17 | SSPreferences* _prefs; 18 | SSAudioDeviceCenter* _deviceCenter; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /src/SSAppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSAppController.m 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/24/09. 6 | // Copyright 2009 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSAppController.h" 10 | #import "SSAboutBox.h" 11 | #import "SSAudioDeviceCenter.h" 12 | #import "SSMenuVolumeSliderView.h" 13 | #import "SSPreferences.h" 14 | #import "SSLegacy.h" 15 | 16 | 17 | @implementation SSAppController 18 | 19 | - (void)_setupModel 20 | { 21 | _prefs = [[SSPreferences alloc] init]; 22 | 23 | _deviceCenter = [[SSAudioDeviceCenter alloc] init]; 24 | 25 | if( [_deviceCenter supportsAudioFollowsJack] ) 26 | [_deviceCenter setAudioFollowsJack: [_prefs audioFollowsJack]]; 27 | 28 | if( [SSLegacy uninstallMenuExtra] ) 29 | [_prefs setOpenAtLogin: YES]; //We uninstalled old SS, probably want this 30 | } 31 | 32 | + (NSImage*)_loadImageNamed: (NSString*)name 33 | { 34 | NSString *path; 35 | NSImage* image = nil; 36 | 37 | path = [[NSBundle bundleForClass: self] pathForImageResource:name]; 38 | if( path ) 39 | image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease]; 40 | return image; 41 | } 42 | 43 | + (NSImage*)_headphonesImage: (BOOL)highlighted 44 | { 45 | static NSImage* bgImage = nil; 46 | static NSImage* bgImageAlt = nil; 47 | 48 | /* Leopard or later system */ 49 | if( bgImage == nil ) 50 | { 51 | { 52 | NSImage* newImg = [NSImage imageNamed: @"menuIconBlack"]; 53 | bgImage = [[NSImage alloc] initWithSize: NSMakeSize( [newImg size].width, [newImg size].height+1.5 )]; 54 | [bgImage lockFocus]; 55 | [newImg drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0]; 56 | // [newImg compositeToPoint: NSZeroPoint operation: NSCompositeSourceOver]; 57 | [bgImage unlockFocus]; 58 | } 59 | 60 | { 61 | NSImage* newImg = [NSImage imageNamed: @"menuIconAlt"]; 62 | bgImageAlt = [[NSImage alloc] initWithSize: NSMakeSize( [newImg size].width, [newImg size].height+1.5 )]; 63 | [bgImageAlt lockFocus]; 64 | [newImg drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0]; 65 | // [newImg compositeToPoint: NSZeroPoint operation: NSCompositeSourceOver]; 66 | [bgImageAlt unlockFocus]; 67 | } 68 | 69 | //bgImage = [[self _loadImageNamed: @"menuIconBlack"] retain]; 70 | //[bgImage setTemplate: YES]; // Leopard only 71 | 72 | //bgImageAlt = [[self _loadImageNamed: @"menuIconAlt"] retain]; 73 | //[bgImageAlt setTemplate: YES]; // Leopard only 74 | } 75 | 76 | return highlighted ? bgImageAlt : bgImage; 77 | } 78 | 79 | - (void)_setupView 80 | { 81 | _statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: 25.0f] retain]; //No, I -don't- know where 25 came from... 82 | 83 | { 84 | [_statusItem setImage: [[self class] _headphonesImage: NO]]; 85 | [_statusItem setAlternateImage: [[self class] _headphonesImage: YES]]; 86 | [_statusItem setHighlightMode: YES]; 87 | } 88 | 89 | { 90 | NSMenu* menu = [[NSMenu alloc] initWithTitle: @"SoundSource status item"]; 91 | [menu setDelegate: self]; 92 | [_statusItem setMenu: menu]; 93 | [menu release]; 94 | } 95 | } 96 | 97 | - (void)applicationDidFinishLaunching: (NSNotification*)note 98 | { 99 | [self _setupModel]; 100 | [self _setupView]; 101 | } 102 | 103 | - (void)applicationWillTerminate:(NSNotification *)notification 104 | { 105 | [_prefs release]; 106 | _prefs = nil; 107 | 108 | [_deviceCenter release]; 109 | _deviceCenter = nil; 110 | 111 | [_statusItem autorelease]; 112 | _statusItem = nil; 113 | } 114 | 115 | #pragma mark - 116 | 117 | - (IBAction)hitOpenAbout: (id)sender 118 | { 119 | [[NSApplication sharedApplication] activateIgnoringOtherApps: YES]; 120 | [[SSAboutBox sharedAboutBox] showWindow: self]; 121 | } 122 | 123 | - (IBAction)hitOpenSoundPrefs: (id)sender 124 | { 125 | [[NSWorkspace sharedWorkspace] openFile: @"/System/Library/PreferencePanes/Sound.prefPane"]; 126 | } 127 | 128 | - (IBAction)hitOpenAMS: (id)sender 129 | { 130 | [[NSWorkspace sharedWorkspace] 131 | launchAppWithBundleIdentifier: @"com.apple.audio.AudioMIDISetup" 132 | options: NSWorkspaceLaunchDefault 133 | additionalEventParamDescriptor: nil 134 | launchIdentifier: nil]; 135 | } 136 | 137 | - (IBAction)hitSelectInputDevice: (id)sender 138 | { 139 | SSAudioDevice* device = [sender representedObject]; 140 | if( device && [device isKindOfClass: [SSAudioDevice class]] ) 141 | [_deviceCenter setSelectedInputDevice: device]; 142 | } 143 | 144 | - (IBAction)hitSelectOutputDevice: (id)sender 145 | { 146 | SSAudioDevice* device = [sender representedObject]; 147 | if( device && [device isKindOfClass: [SSAudioDevice class]] ) 148 | [_deviceCenter setSelectedOutputDevice: device]; 149 | } 150 | 151 | - (IBAction)hitSelectSystemDevice: (id)sender 152 | { 153 | SSAudioDevice* device = [sender representedObject]; 154 | if( device && [device isKindOfClass: [SSAudioDevice class]] ) 155 | [_deviceCenter setSelectedSystemDevice: device]; 156 | } 157 | 158 | - (IBAction)hitAudioFollowsJack: (id)sender 159 | { 160 | [_prefs setAudioFollowsJack: ![_prefs audioFollowsJack]]; 161 | [_deviceCenter setAudioFollowsJack: [_prefs audioFollowsJack]]; 162 | } 163 | 164 | - (IBAction)hitOpenAtLogin: (id)sender 165 | { 166 | [_prefs setOpenAtLogin: ![_prefs openAtLogin]]; 167 | } 168 | 169 | - (void)_menu: (NSMenu*)menu addAudioDevices: (NSArray*)devices selection: (SSAudioDevice*)selectedDevice action: (SEL)action 170 | { 171 | NSMenuItem* menuItem; 172 | 173 | if( ![devices count] ) 174 | { 175 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"" action: nil keyEquivalent: @""]; 176 | [menuItem setIndentationLevel: 1]; 177 | } 178 | else 179 | { 180 | NSEnumerator* deviceEnum; 181 | SSAudioDevice* device; 182 | NSString* deviceName; 183 | 184 | devices = [devices sortedArrayUsingSelector: @selector(compare:)]; 185 | 186 | deviceEnum = [devices objectEnumerator]; 187 | 188 | while( (device = [deviceEnum nextObject]) != nil ) 189 | { 190 | deviceName = [device name]; 191 | if( !deviceName ) 192 | deviceName = @"(Untitled Device)"; 193 | 194 | menuItem = (NSMenuItem*)[menu addItemWithTitle: deviceName action: action keyEquivalent: @""]; 195 | [menuItem setTarget: self]; 196 | [menuItem setRepresentedObject: device]; 197 | [menuItem setState: [device isEqual: selectedDevice]]; 198 | [menuItem setIndentationLevel: 1]; 199 | 200 | if( action == @selector(hitSelectSystemDevice:) ) //:FIXME: Too ugly 201 | [menuItem setEnabled: [device canBeDefaultSystemDevice]]; 202 | else 203 | [menuItem setEnabled: [device canBeDefaultDevice]]; 204 | } 205 | } 206 | } 207 | 208 | - (void)_addSliderItemToMenu: (NSMenu*)menu kind: (int)kind 209 | { 210 | if( floor(NSAppKitVersionNumber) > 824 /*NSAppKitVersionNumber10_4*/ ) 211 | { 212 | NSMenuItem *item = (NSMenuItem*)[menu addItemWithTitle: @"" action: nil keyEquivalent: @""]; 213 | SSMenuVolumeSliderView *view = [[SSMenuVolumeSliderView alloc] initWithFrame: NSMakeRect( 0, 0, 10, 18 ) deviceCenter: _deviceCenter]; 214 | [view setAutoresizingMask: NSViewWidthSizable]; 215 | [view setKind: kind]; 216 | [(id)item setView: view]; 217 | [view release]; 218 | } 219 | } 220 | 221 | - (void)menuNeedsUpdate:(NSMenu*)menu 222 | { 223 | NSMenuItem* menuItem; 224 | 225 | while( [menu numberOfItems] > 0 ) 226 | [menu removeItemAtIndex: 0]; 227 | 228 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Output" action: nil keyEquivalent: @""]; 229 | [self _addSliderItemToMenu: menu kind: kSSMenuVolumeOutputKind]; 230 | [self _menu: menu 231 | addAudioDevices: [_deviceCenter outputDevices] 232 | selection: [_deviceCenter selectedOutputDevice] 233 | action: @selector(hitSelectOutputDevice:)]; 234 | 235 | [menu addItem: [NSMenuItem separatorItem]]; 236 | 237 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Input" action: nil keyEquivalent: @""]; 238 | [self _addSliderItemToMenu: menu kind: kSSMenuVolumeInputKind]; 239 | [self _menu: menu 240 | addAudioDevices: [_deviceCenter inputDevices] 241 | selection: [_deviceCenter selectedInputDevice] 242 | action: @selector(hitSelectInputDevice:)]; 243 | 244 | [menu addItem: [NSMenuItem separatorItem]]; 245 | 246 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"System" action: nil keyEquivalent: @""]; 247 | [self _addSliderItemToMenu: menu kind: kSSMenuVolumeSystemKind]; 248 | [self _menu: menu 249 | addAudioDevices: [_deviceCenter outputDevices] 250 | selection: [_deviceCenter selectedSystemDevice] 251 | action: @selector(hitSelectSystemDevice:)]; 252 | 253 | if( [_deviceCenter supportsAudioFollowsJack] ) 254 | { 255 | [menu addItem: [NSMenuItem separatorItem]]; 256 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Auto-Switch to Headphones" action: @selector(hitAudioFollowsJack:) keyEquivalent: @""]; 257 | [menuItem setState: [_deviceCenter audioFollowsJack]]; 258 | [menuItem setTarget: self]; 259 | } 260 | 261 | [menu addItem: [NSMenuItem separatorItem]]; 262 | 263 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Open Sound Preferences..." action: @selector(hitOpenSoundPrefs:) keyEquivalent: @""]; 264 | [menuItem setTarget: self]; 265 | 266 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Open Audio MIDI Setup..." action: @selector(hitOpenAMS:) keyEquivalent: @""]; 267 | [menuItem setTarget: self]; 268 | [menu addItem: [NSMenuItem separatorItem]]; 269 | 270 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"About SoundSource" action: @selector(hitOpenAbout:) keyEquivalent: @""]; 271 | [menuItem setTarget: self]; 272 | 273 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Open SoundSource at Login" action: @selector(hitOpenAtLogin:) keyEquivalent: @""]; 274 | [menuItem setState: [_prefs openAtLogin]]; 275 | [menuItem setTarget: self]; 276 | 277 | [menu addItem: [NSMenuItem separatorItem]]; 278 | menuItem = (NSMenuItem*)[menu addItemWithTitle: @"Quit SoundSource" action: @selector(terminate:) keyEquivalent: @""]; 279 | [menuItem setTarget: [NSApplication sharedApplication]]; 280 | } 281 | 282 | 283 | 284 | @end 285 | 286 | int main(int argc, char *argv[]) 287 | { 288 | return NSApplicationMain(argc, (const char **) argv); 289 | } 290 | -------------------------------------------------------------------------------- /src/SSAudioDeviceCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSAudioDeviceCenter.h 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 3/23/06. 6 | // Copyright 2006 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SSAudioDevice : NSObject 13 | { 14 | AudioDeviceID _deviceID; 15 | OSType _sourceType; 16 | BOOL _isInput; 17 | } 18 | 19 | - (id)initWithAudioDeviceID: (AudioDeviceID)deviceID source: (OSType)source isInput: (BOOL)flag; 20 | 21 | - (BOOL)isEqual: (SSAudioDevice*)device; 22 | - (NSComparisonResult)compare: (SSAudioDevice*)device; 23 | 24 | - (NSString*)name; 25 | - (BOOL)canBeDefaultDevice; 26 | - (BOOL)canBeDefaultSystemDevice; 27 | 28 | - (AudioDeviceID)coreAudioDeviceID; 29 | - (NSString *)coreAudioDeviceUID; 30 | - (BOOL)coreAudioIsInput; 31 | - (OSType)coreAudioSourceType; 32 | - (OSType)selectedCoreAudioSourceType; 33 | 34 | - (BOOL)isMuted; 35 | - (BOOL)setMuted:(BOOL)isMuted; 36 | 37 | @end 38 | 39 | @interface SSAudioDeviceCenter : NSObject 40 | { 41 | BOOL _audioFollowsJack; 42 | AudioDeviceID _previousDefaultDevice; 43 | } 44 | 45 | - (id)init; 46 | 47 | - (NSArray*)inputDevices; 48 | - (NSArray*)outputDevices; 49 | 50 | - (SSAudioDevice*)deviceWithID: (AudioDeviceID)deviceID isInput: (BOOL)isInput; 51 | - (NSArray*)devicesWithTransportType: (unsigned)type isInput: (BOOL)isInput; 52 | 53 | - (void)setSelectedInputDevice: (SSAudioDevice*)device; 54 | - (SSAudioDevice*)selectedInputDevice; 55 | - (void)setSelectedOutputDevice: (SSAudioDevice*)device; 56 | - (SSAudioDevice*)selectedOutputDevice; 57 | - (void)setSelectedSystemDevice: (SSAudioDevice*)device; 58 | - (SSAudioDevice*)selectedSystemDevice; 59 | 60 | - (float)inputVolume; 61 | - (void)setInputVolume: (float)vol; 62 | - (float)outputVolume; 63 | - (void)setOutputVolume: (float)vol; 64 | - (float)systemVolume; 65 | - (void)setSystemVolume: (float)vol; 66 | 67 | - (BOOL)supportsAudioFollowsJack; 68 | - (void)setAudioFollowsJack: (BOOL)flag; 69 | - (BOOL)audioFollowsJack; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /src/SSAudioDeviceCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSAudioDeviceCenter.m 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 3/23/06. 6 | // Copyright 2006 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSAudioDeviceCenter.h" 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #include 16 | #include 17 | 18 | 19 | @interface SSAudioDeviceCenter (Private) 20 | static OSStatus devicePropertyChanged( AudioDeviceID deviceID, UInt32 inChannel, Boolean isInput, 21 | AudioDevicePropertyID inPropertyID, void *inClientData ); 22 | @end 23 | 24 | @implementation SSAudioDevice 25 | 26 | - (id)initWithAudioDeviceID: (AudioDeviceID)deviceID source: (OSType)source isInput: (BOOL)flag 27 | { 28 | if( deviceID == kAudioDeviceUnknown ) 29 | { 30 | [self release]; 31 | return nil; 32 | } 33 | 34 | if( (self = [super init]) != nil ) 35 | { 36 | _deviceID = deviceID; 37 | _sourceType = source; 38 | _isInput = flag; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)dealloc 45 | { 46 | [super dealloc]; 47 | } 48 | 49 | - (NSString*)description 50 | { 51 | return [NSString stringWithFormat: @"<%@: %@ (%d) %@/%@>", [self class], [self name], (unsigned int)[self coreAudioDeviceID], NSFileTypeForHFSTypeCode([self coreAudioTransportType]), NSFileTypeForHFSTypeCode([self coreAudioSourceType])]; 52 | } 53 | 54 | - (BOOL)isEqual: (SSAudioDevice*)device 55 | { 56 | return ([self coreAudioDeviceID] == [device coreAudioDeviceID]) && 57 | ([self coreAudioSourceType] == [device coreAudioSourceType]) && 58 | ([self coreAudioIsInput] == [device coreAudioIsInput]); 59 | } 60 | 61 | - (NSComparisonResult)compare: (SSAudioDevice*)device 62 | { 63 | return [[self name] caseInsensitiveCompare: [device name]]; 64 | } 65 | 66 | - (NSString*)name 67 | { 68 | OSStatus err; 69 | UInt32 size; 70 | NSString* deviceName = nil; 71 | NSString* sourceName = nil; 72 | 73 | { 74 | size = sizeof(deviceName); 75 | err = AudioDeviceGetProperty( [self coreAudioDeviceID], 0, [self coreAudioIsInput], kAudioDevicePropertyDeviceNameCFString, &size, &deviceName); 76 | if( err ) 77 | deviceName = nil; 78 | } 79 | 80 | if( _sourceType != 0 ) 81 | { 82 | AudioValueTranslation trans; 83 | 84 | trans.mInputData = &_sourceType; 85 | trans.mInputDataSize = sizeof(_sourceType); 86 | trans.mOutputData = &sourceName; 87 | trans.mOutputDataSize = sizeof(sourceName); 88 | size = sizeof(AudioValueTranslation); 89 | err = AudioDeviceGetProperty( [self coreAudioDeviceID] , 0, [self coreAudioIsInput], kAudioDevicePropertyDataSourceNameForIDCFString, &size, &trans); 90 | if( err ) 91 | sourceName = nil; 92 | } 93 | 94 | // If AirPlay or >1 source on device, use DeviceName: SourceName (doesn't match Sound prefpane, but is much easier to understand) 95 | if( ( sourceName && ![sourceName isEqual: deviceName] ) && 96 | ( 97 | ( !AudioDeviceGetPropertyInfo([self coreAudioDeviceID], 0, [self coreAudioIsInput], kAudioDevicePropertyDataSources, &size, NULL) && ( size > sizeof(UInt32) ) 98 | ) || ( [self coreAudioTransportType] == kAudioDeviceTransportTypeAirPlay ) 99 | ) 100 | ) 101 | { 102 | return [NSString stringWithFormat: @"%@: %@", deviceName, sourceName]; 103 | } 104 | 105 | return sourceName ? sourceName : deviceName; 106 | } 107 | 108 | - (BOOL)canBeDefaultDevice 109 | { 110 | OSStatus err; 111 | UInt32 canBe; 112 | UInt32 size = sizeof(canBe); 113 | 114 | err = AudioDeviceGetProperty( [self coreAudioDeviceID], 0, [self coreAudioIsInput], kAudioDevicePropertyDeviceCanBeDefaultDevice, &size, &canBe); 115 | 116 | return (err == noErr) && (canBe == 1); 117 | } 118 | 119 | - (BOOL)canBeDefaultSystemDevice 120 | { 121 | OSStatus err; 122 | UInt32 canBe; 123 | UInt32 size = sizeof(canBe); 124 | 125 | err = AudioDeviceGetProperty( [self coreAudioDeviceID], 0, [self coreAudioIsInput], kAudioDevicePropertyDeviceCanBeDefaultSystemDevice, &size, &canBe); 126 | 127 | return (err == noErr) && (canBe == 1); 128 | } 129 | 130 | - (NSString *)coreAudioDeviceUID 131 | { 132 | OSStatus err; 133 | CFStringRef deviceUID; 134 | UInt32 size = sizeof(deviceUID); 135 | AudioObjectPropertyAddress propertyAddress = { 136 | kAudioDevicePropertyDeviceUID, 137 | kAudioObjectPropertyScopeGlobal, 138 | kAudioObjectPropertyElementWildcard }; 139 | 140 | err = AudioObjectGetPropertyData( [self coreAudioDeviceID], &propertyAddress, 0, NULL, &size, &deviceUID); 141 | 142 | if (err != noErr) 143 | return nil; 144 | 145 | NSString *deviceUIDString = [[(NSString *)deviceUID copy] autorelease]; 146 | CFRelease(deviceUID); 147 | 148 | return deviceUIDString; 149 | } 150 | 151 | - (AudioDeviceID)coreAudioDeviceID 152 | { 153 | return _deviceID; 154 | } 155 | 156 | - (OSType)coreAudioSourceType 157 | { 158 | return _sourceType; 159 | } 160 | 161 | - (OSType)selectedCoreAudioSourceType 162 | { 163 | OSStatus err; 164 | OSType sourceType; 165 | UInt32 size = sizeof(sourceType); 166 | 167 | err = AudioDeviceGetProperty( [self coreAudioDeviceID], 0, [self coreAudioIsInput], kAudioDevicePropertyDataSource, &size, &sourceType); 168 | if( err ) 169 | sourceType = 0; 170 | return sourceType; 171 | } 172 | 173 | - (BOOL)coreAudioIsInput 174 | { 175 | return _isInput; 176 | } 177 | 178 | - (unsigned)coreAudioTransportType 179 | { 180 | OSStatus err; 181 | UInt32 transportType, size = sizeof(transportType); 182 | AudioObjectPropertyAddress address = { 183 | .mSelector = kAudioDevicePropertyTransportType, 184 | .mScope = kAudioObjectPropertyScopeGlobal, 185 | .mElement = kAudioObjectPropertyElementMaster 186 | }; 187 | 188 | err = AudioObjectGetPropertyData( [self coreAudioDeviceID], &address, 0, NULL, &size, &transportType); 189 | if( err ) 190 | return 0; 191 | return transportType; 192 | } 193 | 194 | - (BOOL)isMuted 195 | { 196 | OSStatus err; 197 | UInt32 muted, size = sizeof(muted); 198 | AudioObjectPropertyAddress address = { 199 | .mSelector = kAudioDevicePropertyMute, 200 | .mScope = kAudioDevicePropertyScopeOutput, 201 | .mElement = kAudioObjectPropertyElementMaster 202 | }; 203 | 204 | err = AudioObjectGetPropertyData( [self coreAudioDeviceID], &address, 0, NULL, &size, &muted); 205 | if( err || size != sizeof(muted)) 206 | return NO; 207 | return muted; 208 | } 209 | 210 | - (BOOL)setMuted:(BOOL)isMuted 211 | { 212 | OSStatus err; 213 | UInt32 muted = isMuted; 214 | AudioObjectPropertyAddress address = { 215 | .mSelector = kAudioDevicePropertyMute, 216 | .mScope = kAudioDevicePropertyScopeOutput, 217 | .mElement = kAudioObjectPropertyElementMaster 218 | }; 219 | 220 | err = AudioObjectSetPropertyData( [self coreAudioDeviceID], &address, 0, NULL, sizeof(muted), &muted); 221 | if( err ) 222 | return NO; 223 | return YES; 224 | } 225 | 226 | @end 227 | 228 | #pragma mark - 229 | 230 | @implementation SSAudioDeviceCenter 231 | 232 | // stuff from AudioServices we can't include directly because we need 10.4 compatibility 233 | static OSStatus (*AudioHardwareServiceGetPropertyDataPtr)(AudioObjectID inObjectID, 234 | const AudioObjectPropertyAddress* inAddress, 235 | UInt32 inQualifierDataSize, 236 | const void* inQualifierData, 237 | UInt32* ioDataSize, 238 | void* outData); 239 | static OSStatus (*AudioHardwareServiceSetPropertyDataPtr)(AudioObjectID inObjectID, 240 | const AudioObjectPropertyAddress* inAddress, 241 | UInt32 inQualifierDataSize, 242 | const void* inQualifierData, 243 | UInt32 inDataSize, 244 | const void* inData); 245 | static Boolean (*AudioHardwareServiceHasPropertyPtr)(AudioObjectID inObjectID, 246 | const AudioObjectPropertyAddress* inAddress); 247 | 248 | + (void)initialize 249 | { 250 | NSURL *url = [NSURL fileURLWithPath: @"/System/Library/Frameworks/AudioToolbox.framework"]; 251 | CFBundleRef bundle = CFBundleCreate( NULL, (CFURLRef)url ); 252 | if( bundle ) 253 | { 254 | AudioHardwareServiceGetPropertyDataPtr = CFBundleGetFunctionPointerForName( bundle, CFSTR("AudioHardwareServiceGetPropertyData") ); 255 | AudioHardwareServiceSetPropertyDataPtr = CFBundleGetFunctionPointerForName( bundle, CFSTR("AudioHardwareServiceSetPropertyData") ); 256 | AudioHardwareServiceHasPropertyPtr = CFBundleGetFunctionPointerForName( bundle, CFSTR("AudioHardwareServiceHasProperty") ); 257 | } 258 | } 259 | 260 | - (id)init 261 | { 262 | if( (self = [super init]) != nil ) 263 | { 264 | } 265 | 266 | return self; 267 | } 268 | 269 | - (void)dealloc 270 | { 271 | AudioDeviceRemovePropertyListener( 0, 0, 0, kAudioDevicePropertyDataSource, devicePropertyChanged ); 272 | 273 | [super dealloc]; 274 | } 275 | 276 | - (NSArray*)_allDevicesWithDeviceID: (AudioDeviceID)deviceID isInput: (BOOL)isInput 277 | { 278 | NSMutableArray* objList = [NSMutableArray array]; 279 | UInt32 size; 280 | int i, count; 281 | OSType *list; 282 | SSAudioDevice *device; 283 | 284 | if( !AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyDataSources, &size, NULL) ) 285 | { 286 | count = size / sizeof(OSType); 287 | if( count ) 288 | { 289 | list = alloca(size); 290 | if( !AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyDataSources, &size, list)) 291 | { 292 | for (i = 0; i < count; i++) 293 | { 294 | device = [[SSAudioDevice alloc] initWithAudioDeviceID: deviceID source: list[i] isInput: isInput]; 295 | [objList addObject: device]; 296 | [device release]; 297 | } 298 | } 299 | } 300 | } 301 | 302 | if( ![objList count] ) 303 | { 304 | device = [[SSAudioDevice alloc] initWithAudioDeviceID: deviceID source: 0 isInput: isInput]; 305 | [objList addObject: device]; 306 | [device release]; 307 | } 308 | 309 | return objList; 310 | } 311 | 312 | - (NSArray*)_loadDeviceList: (BOOL)isInput 313 | { 314 | NSMutableArray* deviceList = [NSMutableArray array]; 315 | UInt32 size; 316 | int i, count; 317 | AudioDeviceID* list; 318 | NSArray* tmpList; 319 | 320 | if (AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, NULL)) 321 | return nil; 322 | 323 | count = size / sizeof(AudioDeviceID); 324 | list = (AudioDeviceID *) alloca(count * sizeof(AudioDeviceID)); 325 | if (AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, list)) 326 | return nil; 327 | 328 | for (i = 0; i < count; i++) 329 | { 330 | if (!AudioDeviceGetPropertyInfo(list[i], 0, isInput, kAudioDevicePropertyStreamConfiguration, &size, NULL)) 331 | { 332 | AudioBufferList* bufferList = (AudioBufferList*)malloc(size); 333 | 334 | if (!AudioDeviceGetProperty(list[i], 0, isInput, kAudioDevicePropertyStreamConfiguration, &size, bufferList)) 335 | { 336 | if (bufferList->mNumberBuffers) 337 | { 338 | tmpList = [self _allDevicesWithDeviceID: list[i] isInput: isInput]; 339 | if( tmpList ) 340 | [deviceList addObjectsFromArray: tmpList]; 341 | } 342 | } 343 | 344 | free( bufferList ); 345 | } 346 | } 347 | 348 | return deviceList; 349 | } 350 | 351 | - (NSArray*)inputDevices 352 | { 353 | return [self _loadDeviceList: YES]; 354 | } 355 | 356 | - (NSArray*)outputDevices 357 | { 358 | return [self _loadDeviceList: NO]; 359 | } 360 | 361 | - (SSAudioDevice*)deviceWithID: (AudioDeviceID)deviceID isInput: (BOOL)isInput 362 | { 363 | NSArray* deviceList = isInput ? [self inputDevices] : [self outputDevices]; 364 | NSEnumerator* deviceEnum = [deviceList objectEnumerator]; 365 | SSAudioDevice* device; 366 | 367 | while( (device = [deviceEnum nextObject]) != nil ) 368 | { 369 | if ([device coreAudioDeviceID] == deviceID) { 370 | OSType selectedSourceType = [device selectedCoreAudioSourceType]; 371 | while ([device coreAudioSourceType] != selectedSourceType) { 372 | if ((device = [deviceEnum nextObject]) == nil) 373 | return nil; // ran out of devices while looking 374 | } 375 | return device; 376 | } 377 | } 378 | 379 | return nil; 380 | } 381 | 382 | - (NSArray*)devicesWithTransportType: (unsigned)type isInput: (BOOL)isInput 383 | { 384 | NSArray* deviceList = isInput ? [self inputDevices] : [self outputDevices]; 385 | NSEnumerator* deviceEnum = [deviceList objectEnumerator]; 386 | SSAudioDevice* device; 387 | NSMutableArray* matches = [NSMutableArray array]; 388 | 389 | while( (device = [deviceEnum nextObject]) != nil ) 390 | { 391 | if( [device coreAudioTransportType] == type ) 392 | [matches addObject: device]; 393 | } 394 | 395 | return matches; 396 | } 397 | 398 | #pragma mark - 399 | 400 | - (OSStatus)_setDefaultDeviceOfClass: (OSType)type to: (SSAudioDevice*)device 401 | { 402 | AudioDeviceID deviceID = [device coreAudioDeviceID]; 403 | OSStatus err; 404 | 405 | if( device == nil || deviceID == kAudioDeviceUnknown ) 406 | return paramErr; 407 | 408 | err = AudioHardwareSetProperty(type, sizeof(deviceID), &deviceID); 409 | if( err ) 410 | { 411 | NSLog( @"AudioHardwareSetProperty(%@) Error: %d", NSFileTypeForHFSTypeCode(type), (int)err ); 412 | } 413 | 414 | OSType sourceType = [device coreAudioSourceType]; 415 | if( sourceType != [device selectedCoreAudioSourceType] ) 416 | { 417 | AudioObjectPropertyAddress propertyAddress = { 418 | .mSelector = kAudioDevicePropertyDataSource, 419 | .mScope = (type == kAudioHardwarePropertyDefaultInputDevice) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 420 | .mElement = kAudioObjectPropertyElementMaster 421 | }; 422 | err = AudioObjectSetPropertyData(deviceID, &propertyAddress, 0, NULL, sizeof(sourceType), &sourceType); 423 | if( err ) 424 | { 425 | NSLog( @"AudioObjectSetPropertyData(%@) Error: %d", NSFileTypeForHFSTypeCode(sourceType), (int)err ); 426 | } 427 | } 428 | 429 | return err; 430 | } 431 | 432 | - (SSAudioDevice*)_defaultDeviceOfClass: (OSType)type 433 | { 434 | SSAudioDevice* device; 435 | AudioDeviceID deviceID = kAudioDeviceUnknown; 436 | UInt32 size; 437 | 438 | size = sizeof(deviceID); 439 | if( AudioHardwareGetProperty(type, &size, &deviceID) != noErr ) 440 | return nil; 441 | if( deviceID == kAudioDeviceUnknown ) 442 | return nil; 443 | 444 | device = [self deviceWithID: deviceID isInput: (type == kAudioHardwarePropertyDefaultInputDevice)]; 445 | return device; 446 | } 447 | 448 | - (void)setSelectedInputDevice: (SSAudioDevice*)device 449 | { 450 | [self _setDefaultDeviceOfClass: kAudioHardwarePropertyDefaultInputDevice to: device]; 451 | } 452 | 453 | - (SSAudioDevice*)selectedInputDevice 454 | { 455 | return [self _defaultDeviceOfClass: kAudioHardwarePropertyDefaultInputDevice]; 456 | } 457 | 458 | - (void)setSelectedOutputDevice: (SSAudioDevice*)device 459 | { 460 | [self _setDefaultDeviceOfClass: kAudioHardwarePropertyDefaultOutputDevice to: device]; 461 | } 462 | 463 | - (SSAudioDevice*)selectedOutputDevice 464 | { 465 | return [self _defaultDeviceOfClass: kAudioHardwarePropertyDefaultOutputDevice]; 466 | } 467 | 468 | - (void)setSelectedSystemDevice: (SSAudioDevice*)device 469 | { 470 | [self _setDefaultDeviceOfClass: kAudioHardwarePropertyDefaultSystemOutputDevice to: device]; 471 | } 472 | 473 | - (SSAudioDevice*)selectedSystemDevice 474 | { 475 | return [self _defaultDeviceOfClass: kAudioHardwarePropertyDefaultSystemOutputDevice]; 476 | } 477 | 478 | #pragma mark - 479 | 480 | - (NSString *)_stringForCAError: (OSStatus)err 481 | { 482 | return [(id)UTCreateStringForOSType( err ) autorelease]; 483 | } 484 | 485 | // pass NaN to only get, not set, volume 486 | // returns new volume when setting 487 | // returns NaN on error or if the device doesn't support volume changes 488 | // bits cribbed from http://www.cocoadev.com/index.pl?SoundVolume 489 | // much code courtesy of eddienull 490 | - (float)_getAndSetVolume: (float)newVolume forInput: (BOOL)isInput 491 | { 492 | OSStatus theError = noErr; 493 | AudioObjectPropertyAddress theAddress = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; 494 | AudioObjectID theObject = 0; 495 | UInt32 theDataSize = 0; 496 | Float32 theVolume[2] = { 0, 0 }; 497 | UInt32 theChannels[2] = { kAudioObjectPropertyElementMaster, kAudioObjectPropertyElementMaster }; 498 | int index = 0; 499 | 500 | if (!AudioHardwareServiceGetPropertyDataPtr ) //10.4 is unsupported 501 | return NAN; 502 | 503 | // get default device's object 504 | if( isInput ) 505 | theAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; 506 | else 507 | theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; 508 | 509 | theAddress.mScope = kAudioObjectPropertyScopeGlobal; 510 | theAddress.mElement = kAudioObjectPropertyElementMaster; 511 | theDataSize = sizeof(AudioObjectID); 512 | theError = AudioHardwareServiceGetPropertyDataPtr(kAudioObjectSystemObject, &theAddress, 0, NULL, &theDataSize, &theObject); 513 | if( theError ) 514 | { 515 | NSLog(@"error %@ getting device object", [self _stringForCAError: theError]); 516 | return NAN; 517 | } 518 | 519 | if( isInput ) 520 | theAddress.mScope = kAudioDevicePropertyScopeInput; 521 | else 522 | theAddress.mScope = kAudioDevicePropertyScopeOutput; 523 | 524 | // check to see if a master channel exists 525 | theAddress.mSelector = kAudioDevicePropertyVolumeScalar; 526 | theDataSize = sizeof(Float32); 527 | if(!AudioHardwareServiceHasPropertyPtr(theObject, &theAddress)) 528 | { 529 | // no master channel, try to get preferred stereo channels 530 | // default values: if no preferred stereo channels, just set channel 1 531 | theChannels[0] = 1; 532 | theChannels[1] = 1; 533 | 534 | theAddress.mSelector = kAudioDevicePropertyPreferredChannelsForStereo; 535 | if(AudioHardwareServiceHasPropertyPtr(theObject, &theAddress)) 536 | { 537 | theDataSize = sizeof(theChannels); 538 | theError = AudioHardwareServiceGetPropertyDataPtr(theObject, &theAddress, 0, NULL, &theDataSize, &theChannels); 539 | if( theError ) 540 | { 541 | NSLog(@"error %@ getteing preferred stereo channels", [self _stringForCAError: theError]); 542 | return NAN; 543 | } 544 | } 545 | } 546 | 547 | // if it still doesn't work, just bail out, can't set the volume for this device 548 | { 549 | BOOL hasSettableChannel = NO; 550 | 551 | for(index = 0; index < 2; index++ ) 552 | { 553 | theAddress.mSelector = kAudioDevicePropertyVolumeScalar; 554 | theAddress.mElement = theChannels[index]; 555 | hasSettableChannel = AudioHardwareServiceHasPropertyPtr(theObject, &theAddress); 556 | if( hasSettableChannel ) 557 | break; 558 | } 559 | 560 | if(!hasSettableChannel) 561 | return NAN; 562 | } 563 | 564 | // set the volume if it was requested 565 | if( !isnan( newVolume ) ) 566 | { 567 | theAddress.mSelector = kAudioDevicePropertyVolumeScalar; 568 | theDataSize = sizeof(Float32); 569 | 570 | // do one set per channel 571 | // this will duplicate channels in some cases, but there's no harm 572 | // just some inefficiency 573 | for(index = 0; index < 2; index++ ) 574 | { 575 | theAddress.mElement = theChannels[index]; 576 | theError = AudioHardwareServiceSetPropertyDataPtr(theObject, &theAddress, 0, NULL, theDataSize, &newVolume); 577 | if( theError ) 578 | { 579 | NSLog(@"error %@ setting device volume for channel %ud", [self _stringForCAError: theError], (unsigned int)theAddress.mElement); 580 | return NAN; 581 | } 582 | } 583 | } 584 | 585 | // finally get the current volume 586 | theAddress.mSelector = kAudioDevicePropertyVolumeScalar; 587 | theDataSize = sizeof(Float32); 588 | 589 | // do one get per channel and average the two 590 | // this can duplicate channels again, but again no harm 591 | for(index = 0; index < 2; index++ ) 592 | { 593 | theAddress.mElement = theChannels[index]; 594 | theError = AudioHardwareServiceGetPropertyDataPtr(theObject, &theAddress, 0, NULL, &theDataSize, &theVolume[index]); 595 | if( theError ) 596 | { 597 | NSLog(@"error %@ getting device volume for channel %u", [self _stringForCAError: theError], (unsigned int)theAddress.mElement); 598 | return NAN; 599 | } 600 | } 601 | 602 | return (theVolume[0] + theVolume[1]) / 2.0; 603 | } 604 | 605 | - (float)inputVolume 606 | { 607 | return [self _getAndSetVolume: NAN forInput: YES]; 608 | } 609 | 610 | - (void)setInputVolume: (float)vol 611 | { 612 | [self _getAndSetVolume: vol forInput: YES]; 613 | } 614 | 615 | - (float)outputVolume 616 | { 617 | return [self _getAndSetVolume: NAN forInput: NO]; 618 | } 619 | 620 | - (void)setOutputVolume: (float)vol 621 | { 622 | [self _getAndSetVolume: vol forInput: NO]; 623 | if (vol > 0) 624 | [[self selectedOutputDevice] setMuted: NO]; 625 | } 626 | 627 | static const float kSystemVolumeConversionPower = 1.38; 628 | 629 | CF_ENUM(AudioServicesPropertyID) { 630 | kAudioServicesPropertySystemSoundLevel = 'ssvl' 631 | }; 632 | 633 | - (float)systemVolume 634 | { 635 | float level = 0; 636 | UInt32 volSize = sizeof(level); 637 | OSStatus err = AudioServicesGetProperty(kAudioServicesPropertySystemSoundLevel, 0, NULL, &volSize, &level); 638 | if( err ) 639 | { 640 | NSLog( @"Getting alert volume got error %d", err ); 641 | return NAN; 642 | } 643 | 644 | return powf(level, kSystemVolumeConversionPower); 645 | } 646 | 647 | - (void)setSystemVolume: (float)vol 648 | { 649 | float level = powf( vol, 1.0/kSystemVolumeConversionPower ); 650 | UInt32 levelSize = sizeof(level); 651 | OSStatus err = AudioServicesSetProperty(kAudioServicesPropertySystemSoundLevel, 0, NULL, levelSize, &vol); 652 | if( err ) 653 | NSLog( @"Setting alert volume got error %d", err ); 654 | } 655 | 656 | #pragma mark - 657 | 658 | - (SSAudioDevice*)_headphoneDevice 659 | { 660 | return nil; 661 | } 662 | 663 | - (void)_devicePropertyChanged: (NSDictionary*)args 664 | { 665 | AudioDeviceID deviceID = [[args objectForKey: @"device"] intValue]; 666 | BOOL isInput = [[args objectForKey: @"isInput"] boolValue]; 667 | AudioDeviceID currentDefaultDevice; 668 | OSType changedDeviceSource; 669 | AudioDeviceID newDefaultDevice; 670 | 671 | if( !_audioFollowsJack ) 672 | return; 673 | 674 | changedDeviceSource = [[self deviceWithID: deviceID isInput: isInput] selectedCoreAudioSourceType]; 675 | currentDefaultDevice = [[self selectedOutputDevice] coreAudioDeviceID]; 676 | newDefaultDevice = 0; 677 | 678 | if( changedDeviceSource == kIOAudioOutputPortSubTypeHeadphones ) 679 | { 680 | if( deviceID != currentDefaultDevice ) 681 | { 682 | newDefaultDevice = deviceID; 683 | _previousDefaultDevice = currentDefaultDevice; 684 | } 685 | } 686 | else if( changedDeviceSource == kIOAudioOutputPortSubTypeInternalSpeaker ) 687 | { 688 | if( deviceID == currentDefaultDevice ) 689 | { 690 | newDefaultDevice = _previousDefaultDevice; 691 | } 692 | } 693 | 694 | if( newDefaultDevice ) 695 | { 696 | SSAudioDevice* newDefaultDeviceObj = [self deviceWithID: newDefaultDevice isInput: isInput]; 697 | [self setSelectedOutputDevice: newDefaultDeviceObj]; 698 | } 699 | } 700 | 701 | static OSStatus devicePropertyChanged( AudioDeviceID deviceID, UInt32 inChannel, Boolean isInput, 702 | AudioDevicePropertyID inPropertyID, void *inClientData ) 703 | { 704 | NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 705 | SSAudioDeviceCenter* deviceCenter = (SSAudioDeviceCenter*)inClientData; 706 | NSDictionary* args; 707 | 708 | args = [NSDictionary dictionaryWithObjectsAndKeys: 709 | [NSNumber numberWithInt: deviceID], @"device", 710 | [NSNumber numberWithBool: isInput], @"isInput", 711 | [NSNumber numberWithInt: inPropertyID], @"property", 712 | nil]; 713 | 714 | [deviceCenter performSelectorOnMainThread: @selector(_devicePropertyChanged:) withObject: args waitUntilDone: NO]; 715 | 716 | [pool release]; 717 | return noErr; 718 | } 719 | 720 | - (BOOL)supportsAudioFollowsJack 721 | { 722 | #if 1 723 | char hwNameStr[128]; 724 | size_t hwNameLen = sizeof(hwNameStr); 725 | 726 | if( sysctlbyname( "hw.model", &hwNameStr, &hwNameLen, NULL, 0 ) == 0 ) 727 | { 728 | NSString* hwName = [NSString stringWithCString: hwNameStr encoding: NSASCIIStringEncoding]; 729 | 730 | if( [hwName hasPrefix: @"MacPro"] ) 731 | return YES; 732 | } 733 | 734 | return NO; 735 | #else 736 | return NO; 737 | #endif 738 | } 739 | 740 | - (void)setAudioFollowsJack: (BOOL)flag 741 | { 742 | BOOL isInput = NO; 743 | NSArray* deviceList = isInput ? [self inputDevices] : [self outputDevices]; 744 | NSEnumerator* deviceEnum = [deviceList objectEnumerator]; 745 | SSAudioDevice* device; 746 | OSStatus status; 747 | 748 | if( ![self supportsAudioFollowsJack] ) 749 | return; 750 | 751 | _audioFollowsJack = flag; 752 | _previousDefaultDevice = [[self selectedOutputDevice] coreAudioDeviceID]; 753 | 754 | while( (device = [deviceEnum nextObject]) != nil ) 755 | { 756 | if( [device coreAudioTransportType] == kIOAudioDeviceTransportTypeBuiltIn ) 757 | { 758 | if( flag ) 759 | { 760 | status = AudioDeviceAddPropertyListener( [device coreAudioDeviceID], 0, [device coreAudioIsInput], 761 | kAudioDevicePropertyDataSource, devicePropertyChanged, self); 762 | 763 | if( [device selectedCoreAudioSourceType] == kIOAudioOutputPortSubTypeHeadphones ) 764 | { 765 | if( [device coreAudioDeviceID] != _previousDefaultDevice ) 766 | [self setSelectedOutputDevice: device]; 767 | } 768 | } 769 | else 770 | status = AudioDeviceRemovePropertyListener( [device coreAudioDeviceID], 0, [device coreAudioIsInput], 771 | kAudioDevicePropertyDataSource, devicePropertyChanged ); 772 | } 773 | } 774 | } 775 | 776 | - (BOOL)audioFollowsJack 777 | { 778 | return _audioFollowsJack; 779 | } 780 | 781 | 782 | @end 783 | -------------------------------------------------------------------------------- /src/SSLegacy.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSLegacy.h 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/27/09. 6 | // Copyright 2009 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface SSLegacy : NSObject 13 | { 14 | 15 | } 16 | 17 | + (BOOL)uninstallMenuExtra; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /src/SSLegacy.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSLegacy.m 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/27/09. 6 | // Copyright 2009 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSLegacy.h" 10 | 11 | 12 | @implementation SSLegacy 13 | 14 | + (BOOL)uninstallMenuExtra 15 | { 16 | NSString* prefsPath; 17 | NSData* data; 18 | NSPropertyListFormat format; 19 | NSString* error; 20 | NSDictionary* suisDefaults; 21 | NSMutableArray* menuExtraList; 22 | BOOL matched; 23 | 24 | //Load SUIS prefs file 25 | prefsPath = [@"~/Library/Preferences/com.apple.systemuiserver.plist" stringByExpandingTildeInPath]; 26 | 27 | data = [NSData dataWithContentsOfFile: prefsPath]; 28 | if( !data ) 29 | return NO; 30 | 31 | suisDefaults = [NSPropertyListSerialization propertyListFromData: data 32 | mutabilityOption: NSPropertyListMutableContainers format: &format errorDescription: &error]; 33 | if( ![suisDefaults isKindOfClass: [NSDictionary class]] ) 34 | return NO; 35 | 36 | //Find the list of loaded menu extras 37 | menuExtraList = [suisDefaults objectForKey: @"menuExtras"]; 38 | if( ![menuExtraList isKindOfClass: [NSMutableArray class]] ) 39 | return NO; 40 | 41 | matched = NO; 42 | for( id entry in [[menuExtraList copy] autorelease] ) 43 | { 44 | if( [entry isKindOfClass: [NSString class]] ) 45 | { 46 | if( [[entry lastPathComponent] isEqual: @"SoundSource.menu"] ) //If this is a SoundSource instance 47 | { 48 | [menuExtraList removeObject: entry]; //Remove it 49 | matched = YES; 50 | } 51 | } 52 | } 53 | 54 | if( !matched ) 55 | return NO; 56 | 57 | data = [NSPropertyListSerialization dataFromPropertyList: suisDefaults format: format errorDescription: &error]; 58 | if( data ) 59 | [data writeToFile: prefsPath atomically: YES]; 60 | 61 | [NSTask launchedTaskWithLaunchPath: @"/usr/bin/killall" arguments: [NSArray arrayWithObject: @"SystemUIServer"]]; 62 | 63 | return YES; 64 | 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /src/SSMenuVolumeSliderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSMenuVolumeSliderView.h 3 | // SoundSource 4 | // 5 | // Created by Michael Ash on 2/25/08. 6 | // Copyright 2008 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | enum 13 | { 14 | kSSMenuVolumeOutputKind, 15 | kSSMenuVolumeInputKind, 16 | kSSMenuVolumeSystemKind 17 | }; 18 | 19 | @class SSAudioDeviceCenter; 20 | 21 | @interface SSMenuVolumeSliderView : NSView 22 | { 23 | SSAudioDeviceCenter* _deviceCenter; 24 | int _kind; 25 | NSString* _label; 26 | NSSliderCell* _sliderCell; 27 | } 28 | 29 | - (id)initWithFrame: (NSRect)frame deviceCenter: (SSAudioDeviceCenter *)deviceCenter; 30 | - (void)setKind: (int)kind; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /src/SSMenuVolumeSliderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSMenuVolumeSliderView.m 3 | // SoundSource 4 | // 5 | // Created by Michael Ash on 2/25/08. 6 | // Copyright 2008 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSMenuVolumeSliderView.h" 10 | 11 | #import "SSAudioDeviceCenter.h" 12 | 13 | 14 | @interface SSMenuVolumeSliderView (Private) 15 | 16 | - (float)_getVolume; 17 | - (void)_setVolume: (float)val; 18 | - (NSRect)_sliderFrame; 19 | - (NSDictionary *)_labelAttributes; 20 | - (NSRect)_labelFrame; 21 | - (void)_sliderMoved: (id)sender; 22 | 23 | @end 24 | 25 | 26 | @implementation SSMenuVolumeSliderView 27 | 28 | - (id)initWithFrame: (NSRect)frame deviceCenter: (SSAudioDeviceCenter *)deviceCenter 29 | { 30 | if( (self = [super initWithFrame:frame]) ) 31 | { 32 | _deviceCenter = deviceCenter; 33 | _label = @"Volume:"; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)dealloc 39 | { 40 | [_sliderCell release]; 41 | 42 | [super dealloc]; 43 | } 44 | 45 | #pragma mark - 46 | 47 | - (void)setKind: (int)kind 48 | { 49 | _kind = kind; 50 | } 51 | 52 | #pragma mark - 53 | 54 | - (void)drawRect:(NSRect)rect 55 | { 56 | if( !_sliderCell ) 57 | { 58 | _sliderCell = [[NSSliderCell alloc] init]; 59 | [_sliderCell setMinValue: 0.0]; 60 | [_sliderCell setMaxValue: 1.0]; 61 | [_sliderCell setTarget: self]; 62 | [_sliderCell setAction: @selector( _sliderMoved: )]; 63 | [_sliderCell setContinuous: YES]; 64 | 65 | float curVolume = [self _getVolume]; 66 | if( isnan( curVolume ) ) 67 | { 68 | [_sliderCell setFloatValue: 1.0]; 69 | [_sliderCell setEnabled: NO]; 70 | } 71 | else 72 | { 73 | [_sliderCell setFloatValue: curVolume]; 74 | [_sliderCell setEnabled: YES]; 75 | } 76 | } 77 | 78 | [_label drawAtPoint: [self _labelFrame].origin withAttributes: [self _labelAttributes]]; 79 | [_sliderCell drawWithFrame: [self _sliderFrame] inView: self]; 80 | } 81 | 82 | - (BOOL)acceptsFirstMouse: (NSEvent *)theEvent 83 | { 84 | return YES; 85 | } 86 | 87 | - (void)mouseDown: (NSEvent *)event 88 | { 89 | if( [_sliderCell isEnabled] ) 90 | [_sliderCell trackMouse: event inRect: [self _sliderFrame] ofView: self untilMouseUp: YES]; 91 | } 92 | 93 | - (void)mouseDragged: (NSEvent *)event 94 | { 95 | if( ![_sliderCell isEnabled] ) 96 | return; 97 | 98 | NSRect frame = [self _sliderFrame]; 99 | float thickness = [_sliderCell knobThickness]; 100 | 101 | double minVal = [_sliderCell minValue]; 102 | double maxVal = [_sliderCell maxValue]; 103 | double delta = maxVal - minVal; 104 | 105 | double val = [_sliderCell doubleValue]; 106 | double proportion = (val - minVal) / delta; 107 | 108 | float minX = NSMinX( frame ) + proportion * (NSWidth( frame ) - thickness); 109 | 110 | NSRect knobRect = frame; 111 | knobRect.origin.x = minX; 112 | knobRect.size.width = thickness; 113 | 114 | NSPoint p = [event locationInWindow]; 115 | p = [[event window] convertBaseToScreen: p]; 116 | p = [[self window] convertScreenToBase: p]; 117 | p = [self convertPoint: p fromView: nil]; 118 | 119 | if( NSPointInRect( p, knobRect ) ) 120 | [_sliderCell trackMouse: event inRect: frame ofView: self untilMouseUp: NO]; 121 | } 122 | 123 | @end 124 | 125 | @implementation SSMenuVolumeSliderView (Private) 126 | 127 | - (float)_getVolume 128 | { 129 | switch( _kind ) 130 | { 131 | case kSSMenuVolumeInputKind: 132 | return [_deviceCenter inputVolume]; 133 | case kSSMenuVolumeOutputKind: 134 | return [_deviceCenter outputVolume]; 135 | case kSSMenuVolumeSystemKind: 136 | return [_deviceCenter systemVolume]; 137 | default: 138 | NSLog( @"Can't get volume from unknown device kind %d", _kind ); 139 | return NAN; 140 | } 141 | } 142 | 143 | - (void)_setVolume: (float)val 144 | { 145 | switch( _kind ) 146 | { 147 | case kSSMenuVolumeInputKind: 148 | [_deviceCenter setInputVolume: val]; 149 | break; 150 | case kSSMenuVolumeOutputKind: 151 | [_deviceCenter setOutputVolume: val]; 152 | break; 153 | case kSSMenuVolumeSystemKind: 154 | [_deviceCenter setSystemVolume: val]; 155 | break; 156 | default: 157 | NSLog( @"Can't set volume from unknown device kind %d", _kind ); 158 | break; 159 | } 160 | } 161 | 162 | - (NSRect)_sliderFrame 163 | { 164 | const float minXMargin = 5.0; 165 | const float maxXMargin = 30.0; 166 | 167 | NSRect labelFrame = [self _labelFrame]; 168 | NSRect r = [self bounds]; 169 | r.origin.x = NSMaxX( labelFrame ) + minXMargin; 170 | r.size.width -= r.origin.x + maxXMargin; 171 | 172 | return r; 173 | } 174 | 175 | - (void)_sliderMoved: (id)sender 176 | { 177 | [self _setVolume: [_sliderCell floatValue]]; 178 | } 179 | 180 | - (NSDictionary *)_labelAttributes 181 | { 182 | return [NSDictionary dictionaryWithObjectsAndKeys: 183 | [NSFont menuFontOfSize: [NSFont systemFontSize] + 1], NSFontAttributeName, 184 | [NSColor disabledControlTextColor], NSForegroundColorAttributeName, 185 | nil]; 186 | } 187 | 188 | - (NSRect)_labelFrame 189 | { 190 | const float minXMargin = 30.0; 191 | 192 | NSRect r = [self bounds]; 193 | r.size = [_label sizeWithAttributes: [self _labelAttributes]]; 194 | r.origin.x = minXMargin; 195 | r.origin.y = ([self bounds].size.height - r.size.height) / 2.0 + 2; 196 | 197 | return r; 198 | } 199 | 200 | @end 201 | 202 | -------------------------------------------------------------------------------- /src/SSPreferences.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSPreferences.h 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/12/07. 6 | // Copyright 2007 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface SSPreferences : NSObject 13 | { 14 | LSSharedFileListRef _loginItemsSharedList; 15 | 16 | int _opensAtLogin; 17 | } 18 | 19 | - (void)setAudioFollowsJack: (BOOL)flag; 20 | - (BOOL)audioFollowsJack; 21 | 22 | - (void)setOpenAtLogin: (BOOL)flag; 23 | - (BOOL)openAtLogin; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /src/SSPreferences.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSPreferences.m 3 | // SoundSource 4 | // 5 | // Created by Quentin Carnicelli on 8/12/07. 6 | // Copyright 2007 Rogue Amoeba Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "SSPreferences.h" 10 | 11 | @implementation SSPreferences 12 | 13 | - (void)_invalideOpensAtLoginCache 14 | { 15 | _opensAtLogin = -1; 16 | } 17 | 18 | static void _loginItemsDidChange(LSSharedFileListRef inList, void *context) 19 | { 20 | SSPreferences* prefs = (SSPreferences*)context; 21 | [prefs _invalideOpensAtLoginCache]; 22 | } 23 | 24 | - (id)init 25 | { 26 | if( (self = [super init]) != nil ) 27 | { 28 | _loginItemsSharedList = LSSharedFileListCreate( NULL, kLSSharedFileListSessionLoginItems, 0 ); 29 | LSSharedFileListAddObserver( _loginItemsSharedList, CFRunLoopGetMain(), kCFRunLoopCommonModes, _loginItemsDidChange, self ); 30 | [self _invalideOpensAtLoginCache]; 31 | } 32 | 33 | return self; 34 | } 35 | 36 | - (void)dealloc 37 | { 38 | if( _loginItemsSharedList ) 39 | { 40 | LSSharedFileListRemoveObserver( _loginItemsSharedList, CFRunLoopGetMain(), kCFRunLoopCommonModes, _loginItemsDidChange, self ); 41 | 42 | CFRelease( _loginItemsSharedList ); 43 | _loginItemsSharedList = nil; 44 | } 45 | 46 | [super dealloc]; 47 | } 48 | 49 | - (NSString*)_bundleID 50 | { 51 | return [[NSBundle bundleForClass: [self class]] bundleIdentifier]; 52 | } 53 | 54 | - (void)_storeValue: (id)val forKey: (NSString*)key 55 | { 56 | NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] persistentDomainForName: [self _bundleID]]; 57 | NSMutableDictionary* newDefaults = [defaults mutableCopy]; 58 | 59 | if( newDefaults == nil ) 60 | newDefaults = [NSMutableDictionary dictionary]; 61 | 62 | [newDefaults setObject: val forKey: key]; 63 | [[NSUserDefaults standardUserDefaults] setPersistentDomain: newDefaults forName: [self _bundleID]]; 64 | [[NSUserDefaults standardUserDefaults] synchronize]; 65 | } 66 | 67 | - (id)_loadValueForKey: (NSString*)key 68 | { 69 | NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] persistentDomainForName: [self _bundleID]]; 70 | return [defaults objectForKey: key]; 71 | } 72 | 73 | - (void)setAudioFollowsJack: (BOOL)flag 74 | { 75 | [self _storeValue: [NSNumber numberWithBool: flag] forKey: @"audioFollowsJack"]; 76 | } 77 | 78 | - (BOOL)audioFollowsJack 79 | { 80 | return [[self _loadValueForKey: @"audioFollowsJack"] boolValue]; 81 | } 82 | 83 | 84 | #pragma mark - 85 | 86 | - (LSSharedFileListItemRef)_copySharedFileListItemForBundle 87 | { 88 | CFArrayRef loginItems; 89 | UInt32 seed; 90 | NSURL* ourURL; 91 | LSSharedFileListItemRef foundItem; 92 | 93 | if( !_loginItemsSharedList ) 94 | return nil; 95 | 96 | loginItems = LSSharedFileListCopySnapshot( _loginItemsSharedList, &seed ); 97 | if( !loginItems ) 98 | return nil; 99 | 100 | ourURL = [(NSURL*)CFBundleCopyBundleURL( CFBundleGetMainBundle() ) autorelease]; 101 | 102 | foundItem = nil; 103 | 104 | for( id item in (NSArray*)loginItems ) 105 | { 106 | OSStatus err; 107 | CFURLRef url; 108 | 109 | err = LSSharedFileListItemResolve( (LSSharedFileListItemRef)item, kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes, &url, NULL ); 110 | if( err || !url ) 111 | continue; 112 | 113 | if( [(NSURL*)url isEqual: ourURL] ) 114 | { 115 | foundItem = (LSSharedFileListItemRef)CFRetain(item); 116 | CFRelease( url ); 117 | break; 118 | } 119 | else 120 | { 121 | CFRelease( url ); 122 | } 123 | } 124 | 125 | CFRelease( loginItems ); 126 | 127 | return foundItem; 128 | } 129 | 130 | - (void)setOpenAtLogin: (BOOL)flag 131 | { 132 | LSSharedFileListItemRef item; 133 | 134 | if( !_loginItemsSharedList ) 135 | return; 136 | 137 | if( flag == [self openAtLogin] ) 138 | return; 139 | 140 | if( flag ) //Adding 141 | { 142 | NSDictionary* properties; 143 | 144 | properties = [NSDictionary dictionaryWithObject: [NSNumber numberWithBool:YES] forKey:@"com.apple.loginitem.HideOnLaunch"]; 145 | 146 | item = LSSharedFileListInsertItemURL( _loginItemsSharedList, 147 | kLSSharedFileListItemLast, 148 | NULL, 149 | NULL, 150 | (CFURLRef)[(NSURL*)CFBundleCopyBundleURL( CFBundleGetMainBundle() ) autorelease], 151 | (CFDictionaryRef)properties, 152 | NULL ); 153 | 154 | if( item ) 155 | CFRelease( item ); 156 | } 157 | else //Removing 158 | { 159 | item = [self _copySharedFileListItemForBundle]; 160 | 161 | LSSharedFileListItemRemove( _loginItemsSharedList, item ); 162 | 163 | if( item ) 164 | { 165 | CFRelease( item ); 166 | } 167 | } 168 | 169 | [self _invalideOpensAtLoginCache]; 170 | } 171 | 172 | - (BOOL)openAtLogin 173 | { 174 | if( _opensAtLogin == -1 ) 175 | { 176 | LSSharedFileListItemRef item = [self _copySharedFileListItemForBundle]; 177 | 178 | _opensAtLogin = item ? 1 : 0; 179 | 180 | if( item ) 181 | CFRelease(item); 182 | } 183 | 184 | return _opensAtLogin == 1; 185 | } 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /src/SoundSource_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SoundSource' target in the 'SoundSource' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /src/soundsource/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // soundsource 4 | // 5 | // Created by Nicholas Riley on 3/20/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "SSAudioDeviceCenter.h" 11 | 12 | static void printAudioDevices(const char *title, float volume, NSArray *audioDevices, SSAudioDevice *selected) { 13 | printf("%s ", title); 14 | if (volume == NAN) { 15 | printf("(selected device has no volume adjustment)"); 16 | } else { 17 | printf("(volume %.3f)", volume); 18 | } 19 | printf(":\n"); 20 | 21 | audioDevices = [audioDevices sortedArrayUsingSelector: @selector(compare:)]; 22 | for (SSAudioDevice *device in audioDevices) { 23 | printf("%c %s\n", [device isEqual:selected] ? '*' : ' ', [[device name] UTF8String]); 24 | } 25 | } 26 | 27 | static NSString *trimWhitespace(NSString *deviceName) { 28 | // There's extra whitespace padding after some device names. Compensate for it. 29 | return [deviceName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 30 | } 31 | 32 | static SSAudioDevice *audioDeviceWithName(NSArray *audioDevices, NSString *name) { 33 | for (SSAudioDevice *device in audioDevices) { 34 | if ([name compare:trimWhitespace([device name]) options:NSCaseInsensitiveSearch] == NSOrderedSame) 35 | return device; 36 | } 37 | return nil; 38 | } 39 | 40 | void usage(const char *argv0) { 41 | fprintf(stderr, "usage: %s [-ios] [device]\n", argv0); 42 | fprintf(stderr, " or: %s [-IOS] volume\n", argv0); 43 | fprintf(stderr, " or: %s [-Mm]\n", argv0); 44 | fprintf(stderr, " -i display selected audio input device\n"); 45 | fprintf(stderr, " -o display selected audio output device\n"); 46 | fprintf(stderr, " -s display output device used for alert sounds, sound effects\n"); 47 | fprintf(stderr, " -i device set selected audio input device\n"); 48 | fprintf(stderr, " -o device set selected audio output device\n"); 49 | fprintf(stderr, " -s device set output device used for alert sounds, sound effects\n"); 50 | fprintf(stderr, " -I display selected audio input device's volume\n"); 51 | fprintf(stderr, " -O display selected audio output device's volume\n"); 52 | fprintf(stderr, " -S display alert sounds/sound effects volume\n"); 53 | fprintf(stderr, " -I volume set selected audio input device's volume\n"); 54 | fprintf(stderr, " -O volume set selected audio output device's volume\n"); 55 | fprintf(stderr, " -S volume set alert sounds/sound effects volume\n"); 56 | fprintf(stderr, " -M mute selected audio output device\n"); 57 | fprintf(stderr, " -m unmute selected audio output device\n"); 58 | fprintf(stderr, "With no arguments, displays available/selected (*) devices and volumes.\n"); 59 | fprintf(stderr, "soundsource 3.1 (c) 2014-2019 Nicholas Riley .\n"); 60 | fprintf(stderr, "Portions (c) 2003-2009 Rogue Amoeba Software, LLC.\n"); 61 | exit(1); 62 | } 63 | 64 | int main(int argc, const char * argv[]) { 65 | 66 | @autoreleasepool { 67 | SSAudioDeviceCenter *deviceCenter = [[SSAudioDeviceCenter alloc] init]; 68 | 69 | if (argc < 2) { 70 | const char *outputDevice = [[deviceCenter selectedOutputDevice] isMuted] ? "Output muted" : "Output"; 71 | printAudioDevices(outputDevice, [deviceCenter outputVolume], 72 | [deviceCenter outputDevices], 73 | [deviceCenter selectedOutputDevice]); 74 | printAudioDevices("Input", [deviceCenter inputVolume], 75 | [deviceCenter inputDevices], 76 | [deviceCenter selectedInputDevice]); 77 | printAudioDevices("System", [deviceCenter systemVolume], 78 | [NSArray arrayWithObject:[deviceCenter selectedSystemDevice]], 79 | [deviceCenter selectedSystemDevice]); 80 | return 0; 81 | } 82 | if (argc > 3 || strlen(argv[1]) != 2 || argv[1][0] != '-') { 83 | usage(argv[0]); 84 | } 85 | 86 | char option = argv[1][1]; 87 | SSAudioDevice *device = nil; 88 | float volume = NAN; 89 | 90 | if (argc == 2) { 91 | switch (option) { 92 | case 'i': device = [deviceCenter selectedInputDevice]; break; 93 | case 'o': device = [deviceCenter selectedOutputDevice]; break; 94 | case 's': device = [deviceCenter selectedSystemDevice]; break; 95 | case 'I': volume = [deviceCenter inputVolume]; break; 96 | case 'O': volume = [deviceCenter outputVolume]; break; 97 | case 'S': volume = [deviceCenter systemVolume]; break; 98 | case 'M': 99 | case 'm': 100 | [[deviceCenter selectedOutputDevice] setMuted:option == 'M']; 101 | return 0; 102 | default: 103 | usage(argv[0]); 104 | } 105 | if (device == nil) { 106 | if (volume != NAN) { 107 | printf("%.3f\n", volume); 108 | return 0; 109 | } 110 | fprintf(stderr, "%s: can't get selected information\n", argv[0]); 111 | return 1; 112 | } 113 | puts([[device name] UTF8String]); 114 | return 0; 115 | } 116 | 117 | if (option >= 'i') { 118 | NSString *deviceName = trimWhitespace([[NSString alloc] initWithUTF8String:argv[2]]); 119 | device = audioDeviceWithName( 120 | option == 'i' ? [deviceCenter inputDevices] : [deviceCenter outputDevices], deviceName); 121 | [deviceName release]; 122 | if (device == nil) { 123 | fprintf(stderr, "%s: can't set selected audio device\n", argv[0]); 124 | return 1; 125 | } 126 | } else { 127 | char *end; 128 | volume = strtof(argv[2], &end); 129 | if (end == NULL || *end != '\0' || volume < 0 || volume > 1) 130 | usage(argv[0]); 131 | } 132 | switch (option) { 133 | case 'i': [deviceCenter setSelectedInputDevice:device]; break; 134 | case 'o': [deviceCenter setSelectedOutputDevice:device]; break; 135 | case 's': [deviceCenter setSelectedSystemDevice:device]; break; 136 | case 'I': [deviceCenter setInputVolume:volume]; break; 137 | case 'O': [deviceCenter setOutputVolume:volume]; break; 138 | case 'S': [deviceCenter setSystemVolume:volume]; break; 139 | default: 140 | usage(argv[0]); 141 | } 142 | } 143 | return 0; 144 | } -------------------------------------------------------------------------------- /src/soundsource/soundsource-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'soundsource' target in the 'soundsource' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /src/soundsource/soundsource.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 3/20/13 \" DATE 7 | .Dt soundsource 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm soundsource, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner --------------------------------------------------------------------------------