diff --git a/English.lproj/MainMenu.nib/classes.nib b/English.lproj/MainMenu.nib/classes.nib
index 5b14d8e..e9cb252 100644
--- a/English.lproj/MainMenu.nib/classes.nib
+++ b/English.lproj/MainMenu.nib/classes.nib
@@ -219,8 +219,14 @@
ObjC
OUTLETS
+ _controlsMenuItem
+ NSMenuItem
_fileMenu
NSMenu
+ _outputDevices
+ NSMenuItem
+ _outputDevicesSeperator
+ NSMenuItem
_profileChooser
NSPopUpButton
mAuthenticateButton
diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib
index 8ad0581..f7283b5 100644
--- a/English.lproj/MainMenu.nib/info.nib
+++ b/English.lproj/MainMenu.nib/info.nib
@@ -10,6 +10,7 @@
5
IBOpenObjects
+ 29
344
IBSystem Version
diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib
index 9435948..8807da1 100644
Binary files a/English.lproj/MainMenu.nib/keyedobjects.nib and b/English.lproj/MainMenu.nib/keyedobjects.nib differ
diff --git a/Source/MusicServer/MpdMusicServerClient.m b/Source/MusicServer/MpdMusicServerClient.m
index 4b1544f..7dc2a82 100644
--- a/Source/MusicServer/MpdMusicServerClient.m
+++ b/Source/MusicServer/MpdMusicServerClient.m
@@ -41,7 +41,7 @@ static void MpdClientConnectionChangedCallback(MpdObj *mi, int connect, void *us
@implementation MpdMusicServerClient
+ (unsigned int) capabilities {
- return eMusicClientCapabilitiesRandomizePlaylist;
+ return eMusicClientCapabilitiesRandomizePlaylist | eMusicClientCapabilitiesOutputDevices;
}
- (id) init {
@@ -517,4 +517,21 @@ - (oneway void) playerWindowClosed {
[self resetDelayForStatusUpdateTimer:5];
}
+- (bycopy NSArray*) getOutputDevices {
+ MpdData *data = mpd_server_get_output_devices(mConnection);
+ NSMutableArray *results = [NSMutableArray array];
+ for (; data != NULL; data = mpd_data_get_next(data)) {
+ [results addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:data->output_dev->enabled == 1 ? YES : NO], dEnabled,
+ [NSString stringWithUTF8String:data->output_dev->name], dName,
+ [NSNumber numberWithInt:data->output_dev->id], dId, nil]];
+ }
+
+ return results;
+}
+
+- (void) setOutputDeviceWithId:(int)theId toEnabled:(BOOL)enabled {
+ mpd_server_set_output_device(mConnection, theId, enabled == YES ? 1 : 0);
+}
+
@end
diff --git a/Source/MusicServer/MusicServerClient.h b/Source/MusicServer/MusicServerClient.h
index ef233ae..b3d74b5 100644
--- a/Source/MusicServer/MusicServerClient.h
+++ b/Source/MusicServer/MusicServerClient.h
@@ -68,6 +68,10 @@ extern NSString *dTotalTime;
extern NSString *dElapsedTime;
extern NSString *dPlaylistLength;
+extern NSString *dId;
+extern NSString *dName;
+extern NSString *dEnabled;
+
typedef enum {
eStatePaused,
eStateStopped,
@@ -82,6 +86,7 @@ typedef enum {
typedef enum {
eMusicClientCapabilitiesRandomizePlaylist = 0x0001,
+ eMusicClientCapabilitiesOutputDevices = 0x0002
} MusicClientCapabilities;
@protocol MusicServerClientInterface
@@ -147,6 +152,9 @@ typedef enum {
// needed for randomzing
- (oneway void) swapSongs:(bycopy Song *)srcSong with:(bycopy Song *)destSong;
+
+- (bycopy NSArray*) getOutputDevices;
+- (void) setOutputDeviceWithId:(int)theId toEnabled:(BOOL)enabled;
@end
@interface MusicServerClient : NSObject {
diff --git a/Source/MusicServer/MusicServerClient.m b/Source/MusicServer/MusicServerClient.m
index 9654de2..3fe8c84 100644
--- a/Source/MusicServer/MusicServerClient.m
+++ b/Source/MusicServer/MusicServerClient.m
@@ -63,6 +63,10 @@
NSString *dElapsedTime= @"dElapsedTime";
NSString *dPlaylistLength = @"dPlaylistLength";
+NSString *dId = @"dId";
+NSString *dName = @"dName";
+NSString *dEnabled = @"dEnabled";
+
@implementation MusicServerClient
+ (Class) musicServerClientClassForProfile:(Profile *)aProfile {
switch ([aProfile mode]) {
diff --git a/Source/OutputDevices/OutputDeviceHandler.h b/Source/OutputDevices/OutputDeviceHandler.h
new file mode 100644
index 0000000..4c99a47
--- /dev/null
+++ b/Source/OutputDevices/OutputDeviceHandler.h
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2006-2007 Patrik Weiskircher
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+ */
+
+#import
+
+
+@interface OutputDeviceHandler : NSObject {
+ NSMenu *_menu;
+}
+- (id) initWithMenu:(NSMenu *)theMenu;
+@end
diff --git a/Source/OutputDevices/OutputDeviceHandler.m b/Source/OutputDevices/OutputDeviceHandler.m
new file mode 100644
index 0000000..576a55e
--- /dev/null
+++ b/Source/OutputDevices/OutputDeviceHandler.m
@@ -0,0 +1,103 @@
+//
+// OutputDeviceHandler.m
+// Theremin
+//
+// Created by Patrik Weiskircher on 18.05.09.
+// Copyright 2009 __MyCompanyName__. All rights reserved.
+//
+
+#import "OutputDeviceHandler.h"
+#import "WindowController.h"
+#import "PreferencesController.h"
+
+@interface OutputDeviceHandler (PrivateMethods)
+- (void) discoverOutputDevices;
+- (void) removeMenuItems;
+@end
+
+const static int menuTagOutputDevices = 123411;
+const static int menuTagOutputSeperator = 123412;
+
+@implementation OutputDeviceHandler
+- (id) initWithMenu:(NSMenu *)theMenu {
+ self = [super init];
+ if (self != nil) {
+ _menu = [theMenu retain];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(connected:)
+ name:(NSString *)nMusicServerClientConnected
+ object:nil];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(disconnected:)
+ name:(NSString *)nMusicServerClientDisconnected
+ object:nil];
+
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [_menu release];
+ [super dealloc];
+}
+
+- (void) connected:(NSNotification *)theNotification {
+ if (!([[MusicServerClient musicServerClientClassForProfile:[[[WindowController instance] preferences] currentProfile]] capabilities] & eMusicClientCapabilitiesOutputDevices)) {
+ [self removeMenuItems];
+ } else {
+ [self discoverOutputDevices];
+ }
+}
+
+- (void) disconnected:(NSNotification *)theNotification {
+ [self removeMenuItems];
+}
+
+- (void) removeMenuItems {
+ NSMenuItem *item = [_menu itemWithTag:menuTagOutputDevices];
+ if (item != nil)
+ [_menu removeItemAtIndex:[_menu indexOfItem:item]];
+
+ item = [_menu itemWithTag:menuTagOutputSeperator];
+ if (item != nil)
+ [_menu removeItemAtIndex:[_menu indexOfItem:item]];
+}
+
+- (void) discoverOutputDevices {
+ [self removeMenuItems];
+
+ NSMenuItem *seperatorItem = [NSMenuItem separatorItem];
+ [seperatorItem setTag:menuTagOutputSeperator];
+ [_menu addItem:seperatorItem];
+
+ NSMenuItem *outputDeviceItem = [[[NSMenuItem alloc] initWithTitle:@"Output Devices" action:nil keyEquivalent:@""] autorelease];
+ [outputDeviceItem setSubmenu:[[[NSMenu alloc] init] autorelease]];
+ [outputDeviceItem setTag:menuTagOutputDevices];
+ [_menu addItem:outputDeviceItem];
+
+ NSArray *outputDevices = [[[WindowController instance] musicClient] getOutputDevices];
+ for (int i = 0; i < [outputDevices count]; i++) {
+ NSDictionary *outputDevice = [outputDevices objectAtIndex:i];
+ NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:[outputDevice objectForKey:dName]
+ action:@selector(toggleOutputDevice:)
+ keyEquivalent:@""] autorelease];
+ [item setTarget:self];
+ [item setTag:[[outputDevice objectForKey:dId] intValue]];
+ [item setState:[[outputDevice objectForKey:dEnabled] boolValue] == YES ? NSOnState : NSOffState];
+
+ [[outputDeviceItem submenu] addItem:item];
+ }
+}
+
+- (void) toggleOutputDevice:(NSMenuItem *)sender {
+ BOOL toggleValue = [sender state] == NSOnState ? NO : YES;
+
+ [[[WindowController instance] musicClient] setOutputDeviceWithId:[sender tag] toEnabled:toggleValue];
+
+ [sender setState:toggleValue == YES ? NSOnState : NSOffState];
+}
+
+@end
diff --git a/Source/WindowController.h b/Source/WindowController.h
index 10f9971..74ac272 100644
--- a/Source/WindowController.h
+++ b/Source/WindowController.h
@@ -27,6 +27,7 @@
@class LicenseController, PWVolumeSlider, LibraryController, PWTableView, PWMusicSearchField;
@class InfoAreaController, UpdateDatabaseController, UnifiedToolbar, UnifiedToolbarItem;
@class PlayListFilesController, SongInfoController;
+@class OutputDeviceHandler;
extern NSString *tPlayControlItemIdentifier;
extern NSString *tStopItemIdentifier;
@@ -94,6 +95,9 @@ extern const NSString *dProfile;
RemoteControl *mAppleRemote;
PreferencesWindowController *preferencesWindowController;
+
+ OutputDeviceHandler *_outputDeviceHandler;
+ IBOutlet NSMenuItem *_controlsMenuItem;
}
+ (id) instance;
diff --git a/Source/WindowController.m b/Source/WindowController.m
index 30515b9..3f78d34 100644
--- a/Source/WindowController.m
+++ b/Source/WindowController.m
@@ -53,6 +53,7 @@
#import "ProfileMenuItem.h"
#import "ProfileRepository.h"
#import "PreferencesWindowController.h"
+#import "OutputDeviceHandler.h"
WindowController *globalWindowController = nil;
@@ -268,6 +269,8 @@ - (void) awakeFromNib {
if ([[self preferences] appleRemoteMode] == eAppleRemoteModeAlways)
[[self appleRemote] startListening:self];
+
+ _outputDeviceHandler = [[OutputDeviceHandler alloc] initWithMenu:[_controlsMenuItem submenu]];
}
- (void) dealloc {
@@ -282,6 +285,8 @@ - (void) dealloc {
[mPlayListFilesController release], mPlayListFilesController = nil;
[mPreferencesController release];
+ [_outputDeviceHandler release];
+
[[self appleRemote] stopListening:self];
[super dealloc];
@@ -484,7 +489,7 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item {
else if ([item action] == @selector(detectCompilations:))
return connected && [[self currentLibraryDataSource] supportsDataSourceCapabilities] & eLibraryDataSourceSupportsCustomCompilations;
else if ([item action] == @selector(randomizePlaylist:))
- return connected && [[MusicServerClient musicServerClientClassForProfile:[[self preferences] currentProfile]] capabilities] == eMusicClientCapabilitiesRandomizePlaylist;
+ return connected && [[MusicServerClient musicServerClientClassForProfile:[[self preferences] currentProfile]] capabilities] && eMusicClientCapabilitiesRandomizePlaylist;
return [item isEnabled];
}
diff --git a/Theremin.xcodeproj/project.pbxproj b/Theremin.xcodeproj/project.pbxproj
index 50f59c2..04362ec 100644
--- a/Theremin.xcodeproj/project.pbxproj
+++ b/Theremin.xcodeproj/project.pbxproj
@@ -110,6 +110,7 @@
43BF777A0BB52CCD00715336 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77720BB52CCD00715336 /* MainMenu.nib */; };
43BF777B0BB52CCD00715336 /* PlayListFiles.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77740BB52CCD00715336 /* PlayListFiles.nib */; };
43BF777C0BB52CCD00715336 /* UpdateDatabase.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77760BB52CCD00715336 /* UpdateDatabase.nib */; };
+ 43CA16D80FC1E44200E14FE7 /* OutputDeviceHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */; };
43D0126D0B8B66BA00711A1B /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D0126C0B8B66BA00711A1B /* IOKit.framework */; };
43D0C8B70C13F25900940B39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 43D0C8B50C13F25900940B39 /* Localizable.strings */; };
43D254AA0B4D80F200660DD9 /* MPSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 43D254A90B4D80F200660DD9 /* MPSlider.m */; };
@@ -402,6 +403,8 @@
43BF77750BB52CCD00715336 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/PlayListFiles.nib; sourceTree = ""; };
43BF77770BB52CCD00715336 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/UpdateDatabase.nib; sourceTree = ""; };
43C45DB10F09064300911191 /* ThereminEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThereminEntity.h; path = Source/MusicServer/ThereminEntity.h; sourceTree = ""; };
+ 43CA16D60FC1E44200E14FE7 /* OutputDeviceHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OutputDeviceHandler.h; sourceTree = ""; };
+ 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OutputDeviceHandler.m; sourceTree = ""; };
43D0126C0B8B66BA00711A1B /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; };
43D0C8B60C13F25900940B39 /* English */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = ""; };
43D2549B0B4D80B900660DD9 /* MPSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPSlider.h; path = Source/subclasses/MPSlider.h; sourceTree = ""; };
@@ -506,6 +509,7 @@
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
+ 43CA16D50FC1E42800E14FE7 /* OutputDevices */,
431CCBAE0FBB5F640042E376 /* CoverArt */,
436D30ED0FB72AA30063DC8D /* Growl */,
436DB37B0E52589C00907E0F /* Preferences */,
@@ -867,6 +871,16 @@
name = Categories;
sourceTree = "";
};
+ 43CA16D50FC1E42800E14FE7 /* OutputDevices */ = {
+ isa = PBXGroup;
+ children = (
+ 43CA16D60FC1E44200E14FE7 /* OutputDeviceHandler.h */,
+ 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */,
+ );
+ name = OutputDevices;
+ path = Source/OutputDevices;
+ sourceTree = "";
+ };
43CD56E40B354EBE00C42092 /* libmpdclient */ = {
isa = PBXGroup;
children = (
@@ -1312,6 +1326,7 @@
431CCC2F0FBB6A850042E376 /* ImageDownloader.m in Sources */,
431CCC420FBB6E060042E376 /* CoverArtImageView.m in Sources */,
43156E050FBF097E00EF3ECA /* CoverArtAsker.m in Sources */,
+ 43CA16D80FC1E44200E14FE7 /* OutputDeviceHandler.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};