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