1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2016 Auburn Sounds
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 import std..string;
23 import std.uuid;
24 
25 import derelict.carbon;
26 import derelict.cocoa;
27 
28 import dplug.core.runtime;
29 import dplug.core.random;
30 import dplug.core.nogc;
31 import dplug.au.client;
32 
33 
34 import core.stdc.stdio;
35 
36 // TODO: this file is ugly, ownership is all over the place
37 //       and leaking
38 
39 // Register a view factory object, return the class name.
40 const(char)[] registerCocoaViewFactory() nothrow @nogc
41 {
42     acquireCocoaFunctions();
43     NSApplicationLoad(); // to use Cocoa in Carbon applications
44     DPlugCocoaViewFactory.registerSubclass();
45     return DPlugCocoaViewFactory.customClassName[0..(22 + 36)];
46 }
47 
48 // TODO: this is never called! This was required a long time ago
49 //       but conditions have changed now and we can probably be clean.
50 void unregisterCocoaViewFactory() nothrow @nogc
51 {
52     DPlugCocoaViewFactory.unregisterSubclass();
53     releaseCocoaFunctions();
54 }
55 
56 
57 struct DPlugCocoaViewFactory
58 {
59 nothrow:
60 @nogc:
61 
62     // The custom class we use.
63     static __gshared Class clazz = null;
64 
65     // This class uses a unique class name for each plugin instance
66     static __gshared char[22 + 36 + 1] customClassName;
67     
68 
69     NSView parent;
70     alias parent this;
71 
72     // create from an id
73     this(id id_)
74     {
75         this._id = id_;
76     }
77 
78     ~this()
79     {
80     }
81 
82     static void registerSubclass()
83     {
84         if (clazz !is null) // custom class already registered (TODO: use acquire/release semantics instead)
85             return;
86 
87         generateNullTerminatedRandomUUID!char(customClassName, "DPlugCocoaViewFactory_");
88 
89         clazz = objc_allocateClassPair(cast(Class) lazyClass!"NSObject", customClassName.ptr, 0);
90 
91         class_addMethod(clazz, sel!"description:", cast(IMP) &description, "@@:");
92         class_addMethod(clazz, sel!"interfaceVersion", cast(IMP) &interfaceVersion, "I@:");
93         class_addMethod(clazz, sel!"uiViewForAudioUnit:withSize:", cast(IMP) &uiViewForAudioUnit, "@@:^{ComponentInstanceRecord=[1q]}{CGSize=dd}");
94 
95         // Very important: add an instance variable for the this pointer so that the D object can be
96         // retrieved from an id
97         class_addIvar(clazz, "this", (void*).sizeof, (void*).sizeof == 4 ? 2 : 3, "^v");
98 
99         // Replicates the AUCocoaUIBase protocol.
100         // For host to accept that our object follow AUCocoaUIBase, we replicate AUCocoaUIBase
101         // with the same name and methods.
102         // This protocol has to be created at runtime because we don't have @protocol in D.
103         // http://stackoverflow.com/questions/2615725/how-to-create-a-protocol-at-runtime-in-objective-c
104         {
105 
106             Protocol *protocol = objc_getProtocol("AUCocoaUIBase".ptr);
107 
108             if (protocol == null)
109             {
110                 // create it at runtime
111                 protocol = objc_allocateProtocol("AUCocoaUIBase".ptr);
112                 protocol_addMethodDescription(protocol, sel!"interfaceVersion", "I@:", YES, YES);
113                 protocol_addMethodDescription(protocol, sel!"uiViewForAudioUnit:withSize:", "@@:^{ComponentInstanceRecord=[1q]}{CGSize=dd}", YES, YES);
114                 objc_registerProtocol(protocol);
115             }
116 
117             class_addProtocol(clazz, protocol);
118         }
119         objc_registerClassPair(clazz);
120     }
121 
122     static void unregisterSubclass() nothrow
123     {
124         // TODO: remove leaking of class and protocol
125     }
126 }
127 
128 DPlugCocoaViewFactory getInstance(id anId) nothrow @nogc
129 {
130     // strange thing: object_getInstanceVariable definition is odd (void**)
131     // and only works for pointer-sized values says SO
132     void* thisPointer = null;
133     Ivar var = object_getInstanceVariable(anId, "this", &thisPointer);
134     assert(var !is null);
135     assert(thisPointer !is null);
136     return *cast(DPlugCocoaViewFactory*)thisPointer;
137 }
138 
139 // Overridden function gets called with an id, instead of the self pointer.
140 // So we have to get back the D class object address.
141 // Big thanks to Mike Ash (@macdev)
142 extern(C) nothrow @nogc
143 {
144     id description(id self, SEL selector)
145     {
146         ScopedForeignCallback!(true, false) scopedCallback;
147         scopedCallback.enter();
148 
149         return NSString.stringWith("Filter View"w)._id;
150     }
151 
152     uint interfaceVersion(id self, SEL selector)
153     {
154         return 0;
155     }
156 
157     // Create the Cocoa view and return it
158     id uiViewForAudioUnit(id self, SEL selector, AudioUnit audioUnit, NSSize preferredSize)
159     {
160         ScopedForeignCallback!(true, true) scopedCallback;
161         scopedCallback.enter();
162 
163         AUClient plugin = cast(AUClient)( cast(void*)GetComponentInstanceStorage(audioUnit) );
164         if (plugin)
165         {
166             return cast(id)( plugin.openGUIAndReturnCocoaView() );
167         }
168         else
169             return null;
170     }
171 }