1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2016 Guillaume Piolat
6 
7 Portions copyright other contributors, see each source file for more information
8 
9 This software is provided 'as-is', without any express or implied warranty.  In no event will the authors be held liable for any damages arising from the use of this software.
10 
11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
12 
13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
15 1. This notice may not be removed or altered from any source distribution.
16 */
17 /**
18    Audio Unit plug-in client. Internal. Implementation of the Cocoa View Factory.
19 */
20 module dplug.au.cocoaviewfactory;
21 
22 version(AU):
23 
24 import std.string;
25 import std.uuid;
26 
27 import derelict.carbon;
28 import derelict.cocoa;
29 
30 import dplug.core.runtime;
31 import dplug.core.random;
32 import dplug.core.nogc;
33 import dplug.au.client;
34 
35 
36 import core.stdc.stdio;
37 
38 // TODO: this file is ugly, ownership is all over the place
39 //       and leaking
40 
41 // Register a view factory object, return the class name.
42 const(char)[] registerCocoaViewFactory() nothrow @nogc
43 {
44     acquireCocoaFunctions();
45     NSApplicationLoad(); // to use Cocoa in Carbon applications
46     DPlugCocoaViewFactory.registerSubclass();
47     return DPlugCocoaViewFactory.customClassName[0..(22 + 36)];
48 }
49 
50 // TODO: this is never called! This was required a long time ago
51 //       but conditions have changed now and we can probably be clean.
52 void unregisterCocoaViewFactory() nothrow @nogc
53 {
54     DPlugCocoaViewFactory.unregisterSubclass();
55     releaseCocoaFunctions();
56 }
57 
58 
59 struct DPlugCocoaViewFactory
60 {
61 nothrow:
62 @nogc:
63 
64     // The custom class we use.
65     static __gshared Class clazz = null;
66 
67     // This class uses a unique class name for each plugin instance
68     static __gshared char[22 + 36 + 1] customClassName;
69     
70 
71     NSView parent;
72     alias parent this;
73 
74     // create from an id
75     this(id id_)
76     {
77         this._id = id_;
78     }
79 
80     ~this()
81     {
82     }
83 
84     static void registerSubclass()
85     {
86         if (clazz !is null) // custom class already registered (TODO: use acquire/release semantics instead)
87             return;
88 
89         generateNullTerminatedRandomUUID!char(customClassName, "DPlugCocoaViewFactory_");
90 
91         clazz = objc_allocateClassPair(cast(Class) lazyClass!"NSObject", customClassName.ptr, 0);
92 
93         class_addMethod(clazz, sel!"description:", cast(IMP) &description, "@@:");
94         class_addMethod(clazz, sel!"interfaceVersion", cast(IMP) &interfaceVersion, "I@:");
95         class_addMethod(clazz, sel!"uiViewForAudioUnit:withSize:", cast(IMP) &uiViewForAudioUnit, "@@:^{ComponentInstanceRecord=[1q]}{CGSize=dd}");
96 
97         // Very important: add an instance variable for the this pointer so that the D object can be
98         // retrieved from an id
99         class_addIvar(clazz, "this", (void*).sizeof, (void*).sizeof == 4 ? 2 : 3, "^v");
100 
101         // Replicates the AUCocoaUIBase protocol.
102         // For host to accept that our object follow AUCocoaUIBase, we replicate AUCocoaUIBase
103         // with the same name and methods.
104         // This protocol has to be created at runtime because we don't have @protocol in D.
105         // http://stackoverflow.com/questions/2615725/how-to-create-a-protocol-at-runtime-in-objective-c
106         {
107 
108             Protocol *protocol = objc_getProtocol("AUCocoaUIBase".ptr);
109 
110             if (protocol == null)
111             {
112                 // create it at runtime
113                 protocol = objc_allocateProtocol("AUCocoaUIBase".ptr);
114                 protocol_addMethodDescription(protocol, sel!"interfaceVersion", "I@:", YES, YES);
115                 protocol_addMethodDescription(protocol, sel!"uiViewForAudioUnit:withSize:", "@@:^{ComponentInstanceRecord=[1q]}{CGSize=dd}", YES, YES);
116                 objc_registerProtocol(protocol);
117             }
118 
119             class_addProtocol(clazz, protocol);
120         }
121         objc_registerClassPair(clazz);
122     }
123 
124     static void unregisterSubclass() nothrow
125     {
126         // TODO: remove leaking of class and protocol
127     }
128 }
129 
130 DPlugCocoaViewFactory getInstance(id anId) nothrow @nogc
131 {
132     // strange thing: object_getInstanceVariable definition is odd (void**)
133     // and only works for pointer-sized values says SO
134     void* thisPointer = null;
135     Ivar var = object_getInstanceVariable(anId, "this", &thisPointer);
136     assert(var !is null);
137     assert(thisPointer !is null);
138     return *cast(DPlugCocoaViewFactory*)thisPointer;
139 }
140 
141 // Overridden function gets called with an id, instead of the self pointer.
142 // So we have to get back the D class object address.
143 // Big thanks to Mike Ash (@macdev)
144 extern(C) nothrow @nogc
145 {
146     id description(id self, SEL selector)
147     {
148         ScopedForeignCallback!(true, false) scopedCallback;
149         scopedCallback.enter();
150 
151         return NSString.stringWith("Filter View"w)._id;
152     }
153 
154     uint interfaceVersion(id self, SEL selector)
155     {
156         return 0;
157     }
158 
159     // Create the Cocoa view and return it
160     id uiViewForAudioUnit(id self, SEL selector, AudioUnit audioUnit, NSSize preferredSize)
161     {
162         ScopedForeignCallback!(true, true) scopedCallback;
163         scopedCallback.enter();
164 
165         AUClient plugin;
166 
167         void* res;
168         uint pointerSize = cast(uint)(size_t.sizeof);
169         if (AudioUnitGetProperty (audioUnit, AUClient.kAudioUnitProperty_DPLUG_AUCLIENT_INSTANCE, kAudioUnitScope_Global, 0, &res, &pointerSize) == noErr)
170         {
171             plugin = cast(AUClient)res;
172         }
173 
174         if (plugin)
175         {
176             return cast(id)( plugin.openGUIAndReturnCocoaView() );
177         }
178         else
179         {
180             return null;
181         }
182     }
183 }