alberti42 / Volume-Control

Successor of iTunes Volume Control
GNU General Public License v3.0
207 stars 9 forks source link

Swinsian Support #25

Open ghostforest opened 3 weeks ago

ghostforest commented 3 weeks ago

Im trying to add Swinsian support but I'm having issues getting it done. I used the Doppler.h and repurposed everything to Swinsian, changed the ApplicationIDHandle in AppDelegate, but no luck so far. Swinsian has a scriptable variable called sound volume, same as iTunes.

alberti42 commented 3 weeks ago

Thanks and interesting. I will have a look into it.

Im trying to add Swinsian support but I'm having issues getting it done. I used the Doppler.h and repurposed everything to Swinsian, changed the ApplicationIDHandle in AppDelegate, but no luck so far. Swinsian has a scriptable variable called sound volume, same as iTunes.

-- Reply to this email directly or view it on GitHub: https://github.com/alberti42/Volume-Control/issues/25 You are receiving this because you are subscribed to this thread.

Message ID: @.***>

ghostforest commented 3 weeks ago

I figured a bit more out:

Volume Control recognizes swinisian because an error occurs when I actually want to change volume but this only happens if swinisian is actually playing a track. So I guess volume control finds Swinsian at the correct "com.swinsian.Swinsian" handle.

This is the code for swinisian.h /*

import <AppKit/AppKit.h>

import <ScriptingBridge/ScriptingBridge.h>

@class SwinsianTrack, SwinsianApplication;

enum SwinsianEPlS { SwinsianEPlSStopped = 'kPSS', SwinsianEPlSPlaying = 'kPSP', SwinsianEPlSPaused = 'kPSp' }; typedef enum SwinsianEPlS SwinsianEPlS;

/*

// playable track @interface SwinsianTrack : SBObject

@end

// The application program @interface DopplerApplication : SBApplication

@property (copy, readonly) SwinsianTrack *currentTrack; // the currently playing track @property (readonly) SwinsianEPlS playerState; // the current state of the audio player @property BOOL shuffleEnabled; // are songs played in random order? @property NSInteger soundVolume; // the sound output volume (0 = minimum, 100 = maximum)

@end

And here is the code for the appdelegate: // // AppDelegate.m // iTunes Volume Control // // Created by Andrea Alberti on 25.12.12. // Copyright (c) 2012 Andrea Alberti. All rights reserved. //

import "AppDelegate.h"

import "SystemVolume.h"

import "AccessibilityDialog.h"

import <IOKit/hidsystem/ev_keymap.h>

import "OSD.h"

//This will handle signals for us, specifically SIGTERM. void handleSIGTERM(int sig) { [NSApp terminate:nil]; }

pragma mark - Tapping key stroke events

//static void displayPreferencesChanged(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void *userInfo) { // [[NSNotificationCenter defaultCenter] postNotificationName:@"displayResolutionHasChanged" object:NULL]; //}

CGEventRef event_tap_callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void refcon) { static int previousKeyCode = 0; static bool muteDown = false; NSEvent sysEvent;

if (type == kCGEventTapDisabledByTimeout) {
    //        NSLog(@"Event Taps Disabled! Re-enabling");
    [(__bridge AppDelegate *)(refcon) resetEventTap];
    return event;
}

// No event we care for, then return ASAP
if (type != NX_SYSDEFINED) return event;

sysEvent = [NSEvent eventWithCGEvent:event];
// No need to test event type, we know it is NSSystemDefined, becuase that is the same as NX_SYSDEFINED
// if ([sysEvent subtype] != NX_SUBTYPE_AUX_CONTROL_BUTTONS && [sysEvent subtype] != NX_SUBTYPE_AUX_MOUSE_BUTTONS) return event;
if ([sysEvent subtype] != NX_SUBTYPE_AUX_CONTROL_BUTTONS) return event;

AppDelegate* app=(__bridge AppDelegate *)(refcon);

int keyFlags = ([sysEvent data1] & 0x0000FFFF);
int keyCode = (([sysEvent data1] & 0xFFFF0000) >> 16);
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
bool keyIsRepeat = (keyFlags & 0x1);
CGEventFlags keyModifier = [sysEvent modifierFlags]|0xFFFF;

// store whether Apple CMD modifier has been pressed or not
[app setAppleCMDModifierPressed:(keyModifier&NX_COMMANDMASK)==NX_COMMANDMASK];

switch( keyCode )
{
    case NX_KEYTYPE_MUTE:

        if(previousKeyCode!=keyCode && app->volumeRampTimer)
        {
            [app stopVolumeRampTimer];
        }
        previousKeyCode=keyCode;

        if( keyState == 1 )
        {
            muteDown = true;
            [[NSNotificationCenter defaultCenter] postNotificationName:@"MuteVol" object:NULL];
        }
        else
        {
            muteDown = false;
        }
        return NULL;
        break;
    case NX_KEYTYPE_SOUND_UP:
    case NX_KEYTYPE_SOUND_DOWN:

        if(!muteDown)
        {
            if(previousKeyCode!=keyCode && app->volumeRampTimer)
            {
                [app stopVolumeRampTimer];
            }
            previousKeyCode=keyCode;

            if( keyState == 1 )
            {
                if( !app->volumeRampTimer )
                {
                    if( keyCode == NX_KEYTYPE_SOUND_UP )
                        [[NSNotificationCenter defaultCenter] postNotificationName:(keyIsRepeat?@"IncVolRamp":@"IncVol") object:NULL];
                    else
                        [[NSNotificationCenter defaultCenter] postNotificationName:(keyIsRepeat?@"DecVolRamp":@"DecVol") object:NULL];
                }
            }
            else
            {
                if(app->volumeRampTimer)
                {
                    [app stopVolumeRampTimer];
                }
            }
            return NULL;
        }
        break;
}

return event;

}

pragma mark - Class extension for status menu

@interface AppDelegate () { //StatusItemView _statusBarItemView; NSTimer _statusBarHideTimer; NSPopover _hideFromStatusBarHintPopover; NSTextField _hideFromStatusBarHintLabel; NSTimer *_hideFromStatusBarHintPopoverUpdateTimer;

NSView* _hintView;
NSViewController* _hintVC;

}

@end

pragma mark - Extention music applications

@implementation PlayerApplication

@synthesize currentVolume = _currentVolume;

-(id)initWithBundleIdentifier:(NSString*) bundleIdentifier { if (self = [super init]) { [self setCurrentVolume: -100]; [self setOldVolume: -1]; musicPlayer = [SBApplication applicationWithBundleIdentifier:bundleIdentifier];

}
return self;

}

@end

pragma mark - Implementation AppDelegate

@implementation AppDelegate

// @synthesize AppleRemoteConnected=_AppleRemoteConnected; @synthesize StartAtLogin=_StartAtLogin; @synthesize Tapping=_Tapping; @synthesize UseAppleCMDModifier=_UseAppleCMDModifier; @synthesize AppleCMDModifierPressed=_AppleCMDModifierPressed; @synthesize AutomaticUpdates=_AutomaticUpdates; @synthesize hideFromStatusBar = _hideFromStatusBar; @synthesize hideVolumeWindow = _hideVolumeWindow; @synthesize loadIntroAtStart = _loadIntroAtStart; @synthesize statusBar = _statusBar;

@synthesize iTunesBtn = _iTunesBtn; @synthesize spotifyBtn = _spotifyBtn; @synthesize systemBtn = _systemBtn; @synthesize swinsianBtn = _swinsianBtn;

@synthesize iTunesPerc = _iTunesPerc; @synthesize spotifyPerc = _spotifyPerc; @synthesize systemPerc = _systemPerc; @synthesize swinsianPerc = _swinsianPerc;

@synthesize sparkle_updater = _sparkle_updater;

@synthesize statusMenu = _statusMenu;

static NSTimeInterval volumeRampTimeInterval=0.01f; static NSTimeInterval statusBarHideDelay=10.0f; static NSTimeInterval checkPlayerTimeout=0.3f; static NSTimeInterval updateSystemVolumeInterval=0.1f;

-(void) sendMediaKey: (int)key { // create and send down key event NSEvent* key_event;

key_event = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:CGPointZero modifierFlags:0xa00 timestamp:0 windowNumber:0 context:0 subtype:8 data1:((key << 16) | (0xa << 8)) data2:-1];
CGEventPost(0, key_event.CGEvent);
// NSLog(@"%d keycode (down) sent",key);

// create and send up key event
key_event = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:CGPointZero modifierFlags:0xb00 timestamp:0 windowNumber:0 context:0 subtype:8 data1:((key << 16) | (0xb << 8)) data2:-1];
CGEventPost(0, key_event.CGEvent);
// NSLog(@"%d keycode (up) sent",key);

}

-(void)awakeFromNib { }

-(void)completeInitialization { NSDictionary infoDict = [[NSBundle mainBundle] infoDictionary]; //NSString version = [infoDict objectForKey:@"CFBundleShortVersionString"]; //NSString operatingSystemVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString]; NSString releasesCast = [infoDict objectForKey:@"ReleasesCast"];

SPUUpdater* updater = [[self sparkle_updater] updater];
[updater setFeedURL:[NSURL URLWithString:releasesCast]];
[updater setUpdateCheckInterval:60*60*24*7]; // look for new updates every 7 days

//[[SUUpdater sharedUpdater] setFeedURL:[NSURL URLWithString:[NSString stringWithFormat: @"http://quantum-technologies.iap.uni-bonn.de/alberti/iTunesVolumeControl/VolumeControlCast.xml.php?version=%@&osxversion=%@",version,[operatingSystemVersionString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]]];
//[[SUUpdater sharedUpdater] setUpdateCheckInterval:60*60*24*7]; // look for new updates every 7 days

// [self _loadBezelServices]; // El Capitan and probably older systems
[[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/OSD.framework"] load];
self->OSDManager = NSClassFromString(@"OSDManager");

//[self checkSIPforAppIdentifier:@"com.apple.iTunes" promptIfNeeded:YES];
//[self checkSIPforAppIdentifier:@"com.spotify.client" promptIfNeeded:YES];

if(osxVersion >= 115)
    iTunes = [[PlayerApplication alloc] initWithBundleIdentifier:@"com.apple.Music"];
else
    iTunes = [[PlayerApplication alloc] initWithBundleIdentifier:@"com.apple.iTunes"];

spotify = [[PlayerApplication alloc] initWithBundleIdentifier:@"com.spotify.client"];

swinsian = [[PlayerApplication alloc] initWithBundleIdentifier:@"com.swinsian.Swinsian"]; //com.swinsian.Swinsian

// Force MacOS to ask for authorization to AppleEvents if this was not already given
if([iTunes isRunning])
    [iTunes currentVolume];
if([spotify isRunning])
    [spotify currentVolume];
if([swinsian isRunning])
    [swinsian currentVolume];

systemAudio = [[SystemApplication alloc] initWithVersion:osxVersion];

[self showInStatusBar];   // Install icon into the menu bar

// NSString* iTunesVersion = [[NSString alloc] initWithString:[iTunes version]];
// NSString* spotifyVersion = [[NSString alloc] initWithString:[spotify version]];

[self initializePreferences];

[self setStartAtLogin:[self StartAtLogin] savePreferences:false];

volumeSound = [[NSSound alloc] initWithContentsOfFile:@"/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff" byReference:false];

}

}

}

}

-(void)resetEventTap { CGEventTapEnable(eventTap, _Tapping); }

}

pragma mark - Hide From Status Bar

pragma mark - Music players

pragma mark - NSMenuDelegate

@end

I really just changed any Doppler related line to Swinisian. Not as it should be done but i dont need Doppler. Also when inspecting the apple script function list of Swinisian, it says sound volume (number) vs sound volume (integer) as with Music. I dont know if this could be related.

ghostforest commented 1 week ago

Any updates on this? Still trying to get this to work.

alberti42 commented 1 week ago

I will eventually give it a try. I am sorry, I cannot promise when. Possibly by the end of July. I hope you will understand.

Any updates on this? Still trying to get this to work.

-- Reply to this email directly or view it on GitHub: https://github.com/alberti42/Volume-Control/issues/25#issuecomment-2206026109 You are receiving this because you commented.

Message ID: @.***>

ghostforest commented 1 week ago

Ofc, I am trying to fix it myself but I am not really a programmer.

Changing the volume exits with error code. Seems to be something about memory. This only happens when swinsian is actually playing back music. So the good thing: I managed that volume control recognizes swinsian.

However it seems to break inside the function in appdelegate.m

where double vol2 = [musicPlayer soundVolume]; tries to get the @property of Swinsian.h called soundVolume. However since i just replaced the Doppler keywords with Swinsian, @property NSInteger soundVolume; is still defined as NSInteger and I am thinking that Swinsian may not be returning a different type then NSInteger.

using applescript to check Swinsians functions it seems soundVolume is defined as:

sound volume (number) : the volume. (0 minimum, 100 maximum)

whereas applescript says about Music:

sound volume (integer) : the sound output volume (0 = minimum, 100 = maximum)

So I dont know if the invoked apple script is causing this problem or simply because the soundVolume property is hardcoded to be of type NSInteger.

Eventually I'll find out, but maybe you can hint me.

UPDATE: It seems number in applescript actually means float...jeez alright lets go.

ghostforest commented 1 week ago

I fixed it :)

I'll refactor the code and eventually upload it or send it to you so you can include my code to inlcude swinsian functionality. Just tell me how I can beam it to you :)

alberti42 commented 1 week ago

Did you test first a simple Applescript to show that you can control the volume?

Unfortunately I would have to look more in detail. Did you try to pass it to chatgpt? Clearly the code was written pre-chat-gpt. But it can really help you.

Ofc, I am trying to fix it myself but I am not really a programmer.

Changing the volume exits with error code. Seems to be something about memory. This only happens when swinsian is actually playing back music. So the good thing: I managed that volume control recognizes swinsian.

However it seems to break inside the function in appdelegate.m

  • (double) currentVolume

where double vol2 = [musicPlayer soundVolume]; tries to get the @property of Swinsian.h called soundVolume. However since i just replaced the Doppler keywords with Swinsian, @property NSInteger soundVolume; is still defined as NSInteger and I am thinking that Swinsian may not be returning a different type then NSInteger.

using applescript to check Swinsians functions it seems soundVolume is defined as:

sound volume (number) : the volume. (0 minimum, 100 maximum)

whereas applescript says about Music:

sound volume (integer) : the sound output volume (0 = minimum, 100 = maximum)

So I dont know if the invoked apple script is causing this problem or simply because the soundVolume property is hardcoded to be of type NSInteger.

Eventually I'll find out, but maybe you can hint me.

-- Reply to this email directly or view it on GitHub: https://github.com/alberti42/Volume-Control/issues/25#issuecomment-2206776422 You are receiving this because you commented.

Message ID: @.***>

alberti42 commented 1 week ago

Sounds great. Congratulations!

Yes, in principle you can open a pull request on git. If it is too complicated, just post the code.

I fixed it :)

I'll refactor the code and eventually upload it or send it to you so you can include my code to inlcude swinsian functionality. Just tell me how I can beam it to you :)

-- Reply to this email directly or view it on GitHub: https://github.com/alberti42/Volume-Control/issues/25#issuecomment-2206820956 You are receiving this because you commented.

Message ID: @.***>

ghostforest commented 4 days ago

Another update:

as mentioned I addes Swinsian support, however while doing that I broke Music and probably iTunes support.

Swinsian.h: `/*

import <AppKit/AppKit.h>

import <ScriptingBridge/ScriptingBridge.h>

@class SwinsianTrack, SwinsianApplication;

enum SwinsianEPlS { SwinsianEPlSStopped = 'kPSS', SwinsianEPlSPlaying = 'kPSP', SwinsianEPlSPaused = 'kPSp' }; typedef enum SwinsianEPlS SwinsianEPlS;

/*

// playable track @interface SwinsianTrack : SBObject

@end

// The application program @interface SwinsianApplication : SBApplication

@property (copy, readonly) SwinsianTrack currentTrack; // the currently playing track @property (readonly) SwinsianEPlS playerState; // the current state of the audio player @property BOOL shuffleEnabled; // are songs played in random order? @property NSNumber soundVolume; // the sound output volume (0 = minimum, 100 = maximum)

@end `

Inside AppDelegate.m, i essentially added a Swinsian statement after every Doppler statment. Just copy and paste and change "doppler" to "swinsian". However as Swinsian returns NSNumber, I changed all the properties for soundVolume of iTunes.h Spotify.h and Doppler.h to be of type NSNumber.

And I modified these two functions in AppDelegate.m `- (void) setCurrentVolume:(double)currentVolume { [self setDoubleVolume:currentVolume]; NSNumber *roundedVolume = [NSNumber numberWithInteger:round(currentVolume)]; [musicPlayer setSoundVolume:roundedVolume]; }

It works when swinsian is running and playing music but this line: id soundVolume = [musicPlayer soundVolume]; immediately fails when music is used for music playback. I suppose its because Music returns NSInteger and NSInteger is not an object but a primitive type. I am not completely sure if thats actually what causes it to fail. Program aborts with: "Thread1:EXC_BAD_ACCESS (code=1, address=0x30)"

Possible solutions might be either to get this type distinction working or to check which app actually is playing atm inside the currentVolume function. Or revert the Properties to type NSInteger and use some fancy bitwise operations to extract the float or int values. I dont know.