Added app volume and pan setting to AppleScript

This commit is contained in:
Marcus Wu 2021-03-19 20:29:36 -04:00
parent 2ff4c08e75
commit 9765193c1f
12 changed files with 285 additions and 11 deletions

View file

@ -225,6 +225,7 @@
27FB8C2F1DE468320084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGM_Utils.cpp"; }; };
27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; };
27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; };
9E129A412602AE620005851B /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -433,6 +434,8 @@
27F7D48F1D2483B100821C4B /* BGMDecibel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMDecibel.m; path = "Music Players/BGMDecibel.m"; sourceTree = "<group>"; };
27F7D4911D2484A300821C4B /* Decibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Decibel.h; path = "Music Players/Decibel.h"; sourceTree = "<group>"; };
27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BGM_Utils.cpp; path = ../SharedSource/BGM_Utils.cpp; sourceTree = "<group>"; };
9E129A3F2602AE620005851B /* BGMASApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMASApplication.h; path = Scripting/BGMASApplication.h; sourceTree = "<group>"; };
9E129A402602AE620005851B /* BGMASApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMASApplication.m; path = Scripting/BGMASApplication.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -562,6 +565,8 @@
1C2FC31D1EC723A100A76592 /* BGMASOutputDevice.h */,
1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */,
1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */,
9E129A3F2602AE620005851B /* BGMASApplication.h */,
9E129A402602AE620005851B /* BGMASApplication.m */,
);
name = Scripting;
sourceTree = "<group>";
@ -1152,6 +1157,7 @@
1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,
1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */,
9E129A412602AE620005851B /* BGMASApplication.m in Sources */,
1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */,
1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */,
1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */,

View file

@ -24,6 +24,7 @@
// Local Includes
#import "BGMAudioDeviceManager.h"
#import "BGMAppVolumesController.h"
// System Includes
#import <Cocoa/Cocoa.h>
@ -53,6 +54,7 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;
@property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped;
@property (readonly) BGMAudioDeviceManager* audioDevices;
@property BGMAppVolumesController* appVolumes;
@end

View file

@ -64,7 +64,6 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
BGMAutoPauseMenuItem* autoPauseMenuItem;
BGMMusicPlayers* musicPlayers;
BGMSystemSoundsVolume* systemSoundsVolume;
BGMAppVolumesController* appVolumes;
BGMOutputDeviceMenuSection* outputDeviceMenuSection;
BGMPreferencesMenu* prefsMenu;
BGMDebugLoggingMenuItem* debugLoggingMenuItem;
@ -73,6 +72,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
}
@synthesize audioDevices = audioDevices;
@synthesize appVolumes = appVolumes;
- (void) awakeFromNib {
[super awakeFromNib];

View file

@ -44,6 +44,9 @@
- (void) removeAllAppVolumeMenuItems;
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app;
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;
@end
// Protocol for the UI custom classes

View file

@ -124,6 +124,79 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
}
}
- (NSMenuItem*) getMenuItemForApp:(NSRunningApplication*)app {
NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;
for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {
NSMenuItem* item = [bgmMenu itemAtIndex:i];
NSRunningApplication* itemApp = item.representedObject;
BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);
if ([itemApp isEqual:app]) {
return item;
}
}
for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {
NSMenuItem* item = [moreAppsMenu itemAtIndex:i];
NSRunningApplication* itemApp = item.representedObject;
BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);
if ([itemApp isEqual:app]) {
return item;
}
}
return nil;
}
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app {
BGMAppVolumeAndPan result = {
.volume = -1,
.pan = -1
};
NSMenuItem *item = [self getMenuItemForApp:app];
if (item == nil) {
return result;
}
for (NSView* subview in item.view.subviews) {
// Get the volume.
if ([subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
result.volume = [(BGMAVM_VolumeSlider*)subview intValue];
}
// Get the pan position.
if ([subview isKindOfClass:[BGMAVM_PanSlider class]]) {
result.pan = [(BGMAVM_PanSlider*)subview intValue];
}
}
return result;
}
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
NSMenuItem *item = [self getMenuItemForApp:app];
if (item == nil) {
return;
}
for (NSView* subview in item.view.subviews) {
// Get the volume.
if (volumeAndPan.volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
[(BGMAVM_VolumeSlider*)subview setRelativeVolume:volumeAndPan.volume];
}
// Get the pan position.
if (volumeAndPan.pan != -1 && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
[(BGMAVM_PanSlider*)subview setPanPosition:volumeAndPan.pan];
}
}
}
// Create a blank menu item to copy as a template.
- (NSMenuItem*) createBlankAppVolumeMenuItem {
NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];

View file

@ -29,6 +29,11 @@
#pragma clang assume_nonnull begin
typedef struct BGMAppVolumeAndPan {
int volume;
int pan;
} BGMAppVolumeAndPan;
@interface BGMAppVolumesController : NSObject
- (id) initWithMenu:(NSMenu*)menu
@ -45,6 +50,9 @@ forAppWithProcessID:(pid_t)processID
forAppWithProcessID:(pid_t)processID
bundleID:(NSString* __nullable)bundleID;
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app;
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;
@end
#pragma clang assume_nonnull end

View file

@ -40,11 +40,6 @@
#pragma clang assume_nonnull begin
typedef struct BGMAppVolumeAndPan {
int volume;
int pan;
} BGMAppVolumeAndPan;
@implementation BGMAppVolumesController {
// The App Volumes UI.
BGMAppVolumes* appVolumes;
@ -104,6 +99,20 @@ typedef struct BGMAppVolumeAndPan {
}
}
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app {
return [appVolumes getVolumeAndPanForApp:app];
}
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
[appVolumes setVolumeAndPan:volumeAndPan forApp:app];
if (volumeAndPan.volume != -1) {
[self setVolume:volumeAndPan.volume forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
}
if (volumeAndPan.pan != -1) {
[self setPanPosition:volumeAndPan.pan forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
}
}
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app
fromVolumes:(const CACFArray&)volumes {
BGMAppVolumeAndPan volumeAndPan = {

View file

@ -0,0 +1,31 @@
//
// BGMASApplication.h
// Background Music
//
// Created by Marcus Wu on 3/17/21.
// Copyright © 2021 Background Music contributors. All rights reserved.
//
// Local Includes
#import "BGMAppVolumesController.h"
// System Includes
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BGMASApplication : NSObject
- (instancetype) initWithApplication:(NSRunningApplication*)app
volumeController:(BGMAppVolumesController*)volumeController
parentSpecifier:(NSScriptObjectSpecifier* __nullable)parentSpecifier
index:(int)i;
@property (readonly) NSString* name;
@property int volume;
@property int pan;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,69 @@
//
// BGMASApplication.m
// Background Music
//
// Created by Marcus Wu on 3/17/21.
// Copyright © 2021 Background Music contributors. All rights reserved.
//
// Self Include
#import "BGMASApplication.h"
@implementation BGMASApplication {
NSScriptObjectSpecifier* parentSpecifier;
NSRunningApplication *application;
BGMAppVolumesController* appVolumesController;
int index;
}
- (instancetype) initWithApplication:(NSRunningApplication*)app
volumeController:(BGMAppVolumesController*)volumeController
parentSpecifier:(NSScriptObjectSpecifier* __nullable)parent
index:(int)i {
if ((self = [super init])) {
parentSpecifier = parent;
application = app;
appVolumesController = volumeController;
index = i;
}
return self;
}
- (NSString*) name {
return [NSString stringWithFormat:@"%@", [application localizedName]];
}
- (int) volume {
return [appVolumesController getVolumeAndPanForApp:application].volume;
}
- (void) setVolume:(int)vol {
BGMAppVolumeAndPan volume = {
.volume = vol,
.pan = -1
};
[appVolumesController setVolumeAndPan:volume forApp:application];
}
- (int) pan {
return [appVolumesController getVolumeAndPanForApp:application].pan;
}
- (void) setPan:(int)pan {
BGMAppVolumeAndPan thePan = {
.volume = -1,
.pan = pan
};
[appVolumesController setVolumeAndPan:thePan forApp:application];
}
- (NSScriptObjectSpecifier* __nullable) objectSpecifier {
NSScriptClassDescription* parentClassDescription = [parentSpecifier keyClassDescription];
return [[NSNameSpecifier alloc] initWithContainerClassDescription:parentClassDescription
containerSpecifier:parentSpecifier
key:@"applications"
name:self.name];
}
@end

View file

@ -9,8 +9,7 @@
<class name="output device"
code="aDev"
description="A hardware device that can play audio"
plural="output devices"
inherits="item">
plural="output devices">
<synonym name="audio device"/>
<cocoa class="BGMASOutputDevice"/>
@ -19,14 +18,49 @@
code="pnam"
description="The name of the output device."
type="text"
access="r"/>
access="r">
<cocoa key="name"/>
</property>
<property name="selected"
code="Slcd"
type="boolean"
access="rw"
description="Is this the device to be used for audio output?">
<synonym name="default"/>
<cocoa key="selected"/>
</property>
</class>
<class name="audio application"
code="aApp"
description="An application that can play audio"
plural="audio applications">
<synonym name="audio app"/>
<cocoa class="BGMASApplication"/>
<property name="name"
code="pnam"
description="The name of the application."
type="text"
access="r">
<cocoa key="name"/>
</property>
<property name="vol"
code="pVol"
type="integer"
access="rw"
description="The volume setting of the application">
<cocoa key="volume"/>
</property>
<property name="pan"
code="pPan"
type="integer"
access="rw"
description="The pan setting of the application">
<cocoa key="pan"/>
</property>
</class>
@ -48,11 +82,23 @@
<cocoa key="selectedOutputDevice"/>
</property>
<property name="output volume"
type="real"
code="oVol"
access="rw"
description="The main output volume">
<synonym name="main volume"/>
<cocoa key="mainVolume"/>
</property>
<!-- Unintuitively, this is for the array of output devices. -->
<element type="output device" access="r">
<cocoa key="outputDevices"/>
</element>
<element type="audio application" access="r">
<cocoa key="applications"/>
</element>
</class>
</suite>
</dictionary>

View file

@ -24,6 +24,7 @@
// Local Includes
#import "BGMASOutputDevice.h"
#import "BGMASApplication.h"
// System Includes
#import <Foundation/Foundation.h>
@ -37,6 +38,8 @@
@property BGMASOutputDevice* selectedOutputDevice;
@property (readonly) NSArray<BGMASOutputDevice*>* outputDevices;
@property double mainVolume;
@property (readonly) NSArray<BGMASApplication*>* applications;
@end

View file

@ -30,6 +30,7 @@
#import "CAHALAudioSystemObject.h"
#import "CAAutoDisposer.h"
const AudioObjectPropertyScope kScope = kAudioDevicePropertyScopeOutput;
#pragma clang assume_nonnull begin
@ -43,7 +44,7 @@
[key UTF8String]);
}
return [@[@"selectedOutputDevice", @"outputDevices"] containsObject:key];
return [@[@"selectedOutputDevice", @"outputDevices", @"mainVolume", @"applications"] containsObject:key];
}
- (BGMASOutputDevice*) selectedOutputDevice {
@ -83,6 +84,29 @@
return outputDevices;
}
- (double) mainVolume {
BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];
return bgmDevice.GetVolumeControlScalarValue(kScope, kMasterChannel);
}
- (void) setMainVolume:(double)mainVolume {
BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];
bgmDevice.SetMasterVolumeScalar(kScope, (Float32)mainVolume);
}
- (NSArray<BGMASApplication*>*) applications {
NSArray<NSRunningApplication*>* apps = [[NSWorkspace sharedWorkspace] runningApplications];
NSMutableArray<BGMASApplication*>* applications = [NSMutableArray arrayWithCapacity:[apps count]];
for (UInt32 i = 0; i < [apps count]; i++) {
BGMASApplication *app = [[BGMASApplication alloc] initWithApplication:apps[i] volumeController:self.appVolumes parentSpecifier:[self objectSpecifier] index:i];
[applications addObject:app];
}
return applications;
}
@end
#pragma clang assume_nonnull end