Refactoring: Pull all fullscreen code out of Document and into its own helper class
[WebKit-https.git] / Examples / NetscapeMuteAPIPlugin / main.m
1 /*
2  IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
3  consideration of your agreement to the following terms, and your use, installation, 
4  modification or redistribution of this Apple software constitutes acceptance of these 
5  terms.  If you do not agree with these terms, please do not use, install, modify or 
6  redistribute this Apple software.
7  
8  In consideration of your agreement to abide by the following terms, and subject to these 
9  terms, Apple grants you a personal, non-exclusive license, under Appleā€™s copyrights in 
10  this original Apple software (the "Apple Software"), to use, reproduce, modify and 
11  redistribute the Apple Software, with or without modifications, in source and/or binary 
12  forms; provided that if you redistribute the Apple Software in its entirety and without 
13  modifications, you must retain this notice and the following text and disclaimers in all 
14  such redistributions of the Apple Software.  Neither the name, trademarks, service marks 
15  or logos of Apple Inc. may be used to endorse or promote products derived from 
16  the Apple Software without specific prior written permission from Apple. Except as expressly
17  stated in this notice, no other rights or licenses, express or implied, are granted by Apple
18  herein, including but not limited to any patent rights that may be infringed by your 
19  derivative works or by other works in which the Apple Software may be incorporated.
20  
21  The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES, 
22  EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, 
23  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS 
24  USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
25  
26  IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 
27  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
28  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
29  REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND 
30  WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR 
31  OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #import "AudioPlayer.h"
35 #import "MenuHandler.h"
36 #import <Cocoa/Cocoa.h>
37 #import <WebKit/npapi.h>
38 #import <WebKit/npfunctions.h>
39 #import <WebKit/npruntime.h>
40
41 // Browser function table
42 static NPNetscapeFuncs* browser;
43
44 // Structure for per-instance storage
45 @interface PluginObject : NSObject <AudioPlayerDelegate> {
46     AudioPlayer *_audioPlayer;
47     MenuHandler *_menuHandler;
48 }
49
50 @property (nonatomic, readonly) NPP npp;
51 @property (nonatomic) NPWindow window;
52 @property (nonatomic, strong, readonly) NSURL *audioURL;
53 @property (nonatomic, copy, readonly) NSString *displayString;
54 @property (nonatomic, strong, readonly) MenuHandler *menuHandler;
55 @property (nonatomic) BOOL hasFocus;
56 @property (nonatomic, readonly, getter=isPlayingAudio) BOOL playingAudio;
57 @property (nonatomic, getter=isMuted) BOOL muted;
58
59 - (instancetype)initWithNPP:(NPP)npp audioURL:(NSURL *)audioURL;
60
61 @end
62
63 NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData*);
64 NPError NPP_Destroy(NPP instance, NPSavedData**);
65 NPError NPP_SetWindow(NPP instance, NPWindow*);
66 NPError NPP_NewStream(NPP instance, NPMIMEType, NPStream*, NPBool seekable, uint16_t* stype);
67 NPError NPP_DestroyStream(NPP instance, NPStream*, NPReason);
68 int32_t NPP_WriteReady(NPP instance, NPStream*);
69 int32_t NPP_Write(NPP instance, NPStream*, int32_t offset, int32_t len, void* buffer);
70 void NPP_StreamAsFile(NPP instance, NPStream*, const char* fname);
71 void NPP_Print(NPP instance, NPPrint* platformPrint);
72 int16_t NPP_HandleEvent(NPP instance, void* event);
73 void NPP_URLNotify(NPP instance, const char* URL, NPReason, void* notifyData);
74 NPError NPP_GetValue(NPP instance, NPPVariable, void *value);
75 NPError NPP_SetValue(NPP instance, NPNVariable, void *value);
76
77 #pragma export on
78 // Mach-o entry points
79 NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
80 NPError NP_GetEntryPoints(NPPluginFuncs *);
81 void NP_Shutdown(void);
82 #pragma export off
83
84 static void invalidatePlugin(PluginObject *);
85
86 @implementation PluginObject
87
88 - (instancetype)initWithNPP:(NPP)npp audioURL:(NSURL *)audioURL
89 {
90     if (!(self = [super init]))
91         return nil;
92
93     _npp = npp;
94     _audioURL = [audioURL retain];
95     _audioPlayer = [[AudioPlayer alloc] initWithURL:audioURL];
96     _audioPlayer.audioPlayerDelegate = self;
97
98     NPBool isMuted;
99     if (browser->getvalue(npp, NPNVmuteAudioBool, &isMuted) != NPERR_NO_ERROR)
100         isMuted = FALSE;
101     self.muted = isMuted;
102
103     [self _invalidateDisplayString];
104
105     return self;
106 }
107
108 - (void)dealloc
109 {
110     [_audioURL release];
111     [_menuHandler release];
112     [_audioPlayer release];
113     [_displayString release];
114
115     [super dealloc];
116 }
117
118 - (MenuHandler *)menuHandler
119 {
120     if (!_menuHandler)
121         _menuHandler = [[MenuHandler alloc] initWithAudioPlayer:_audioPlayer];
122
123     return _menuHandler;
124 }
125
126 - (BOOL)isPlayingAudio
127 {
128     return _audioPlayer.isPlaying;
129 }
130
131 - (BOOL)isMuted
132 {
133     return _audioPlayer.isMuted;
134 }
135
136 - (void)setMuted:(BOOL)muted
137 {
138     _audioPlayer.muted = muted;
139 }
140
141 - (void)readyStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
142 {
143     [self _invalidateDisplayString];
144     invalidatePlugin(self);
145 }
146
147 - (void)mutedStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
148 {
149 }
150
151 - (void)playStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
152 {
153     [self _invalidateDisplayString];
154     invalidatePlugin(self);
155     browser->setvalue(_npp, NPPVpluginIsPlayingAudio, _audioPlayer.isPlaying);
156 }
157
158 - (void)_invalidateDisplayString
159 {
160     [_displayString release];
161
162     NSString *prefix;
163     if (!_audioPlayer.isReadyToPlay)
164         prefix = @"Loading: ";
165     else if (_audioPlayer.isPlaying)
166         prefix = @"Playing: ";
167     else
168         prefix = @"Ready to play: ";
169
170     _displayString = [[NSString stringWithFormat:@"%@ %@", prefix, _audioURL.absoluteString] retain];
171 }
172
173 @end
174
175
176 NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
177 {
178     browser = browserFuncs;
179     return NPERR_NO_ERROR;
180 }
181
182 NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
183 {
184     pluginFuncs->version = 11;
185     pluginFuncs->size = sizeof(pluginFuncs);
186     pluginFuncs->newp = NPP_New;
187     pluginFuncs->destroy = NPP_Destroy;
188     pluginFuncs->setwindow = NPP_SetWindow;
189     pluginFuncs->newstream = NPP_NewStream;
190     pluginFuncs->destroystream = NPP_DestroyStream;
191     pluginFuncs->asfile = NPP_StreamAsFile;
192     pluginFuncs->writeready = NPP_WriteReady;
193     pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
194     pluginFuncs->print = NPP_Print;
195     pluginFuncs->event = NPP_HandleEvent;
196     pluginFuncs->urlnotify = NPP_URLNotify;
197     pluginFuncs->getvalue = NPP_GetValue;
198     pluginFuncs->setvalue = NPP_SetValue;
199     
200     return NPERR_NO_ERROR;
201 }
202
203 void NP_Shutdown(void)
204 {
205
206 }
207
208 NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
209 {
210     // Create per-instance storage
211     // Ask the browser if it supports the CoreGraphics drawing model
212     NPBool supportsCoreGraphics;
213     if (browser->getvalue(instance, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) != NPERR_NO_ERROR)
214         supportsCoreGraphics = FALSE;
215     
216     if (!supportsCoreGraphics)
217         return NPERR_INCOMPATIBLE_VERSION_ERROR;
218     
219     // If the browser supports the CoreGraphics drawing model, enable it.
220     browser->setvalue(instance, NPPVpluginDrawingModel, (void *)NPDrawingModelCoreGraphics);
221
222     // If the browser supports the Cocoa event model, enable it.
223     NPBool supportsCocoa;
224     if (browser->getvalue(instance, NPNVsupportsCocoaBool, &supportsCocoa) != NPERR_NO_ERROR)
225         supportsCocoa = FALSE;
226     
227     if (!supportsCocoa)
228         return NPERR_INCOMPATIBLE_VERSION_ERROR;
229     
230     browser->setvalue(instance, NPPVpluginEventModel, (void *)NPEventModelCocoa);
231
232     NSURL *audioURL = nil;
233     for (int16_t i = 0; i < argc; i++) {
234         if (!strcasecmp(argn[i], "audiourl")) {
235             NSString *urlString = [NSString stringWithUTF8String:argv[i]];
236             if (urlString)
237                 audioURL = [NSURL URLWithString:urlString];
238             break;
239         }
240     }
241
242     PluginObject *obj = [[PluginObject alloc] initWithNPP:instance audioURL:audioURL];
243     instance->pdata = obj;
244
245     return NPERR_NO_ERROR;
246 }
247
248 NPError NPP_Destroy(NPP instance, NPSavedData** save)
249 {
250     // Free per-instance storage
251     PluginObject *obj = instance->pdata;
252     [obj release];
253
254     return NPERR_NO_ERROR;
255 }
256
257 NPError NPP_SetWindow(NPP instance, NPWindow* window)
258 {
259     PluginObject *obj = instance->pdata;
260     obj.window = *window;
261
262     return NPERR_NO_ERROR;
263 }
264
265 NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
266 {
267     *stype = NP_ASFILEONLY;
268     return NPERR_NO_ERROR;
269 }
270
271 NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
272 {
273     return NPERR_NO_ERROR;
274 }
275
276 int32_t NPP_WriteReady(NPP instance, NPStream* stream)
277 {
278     return 0;
279 }
280
281 int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
282 {
283     return 0;
284 }
285
286 void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
287 {
288 }
289
290 void NPP_Print(NPP instance, NPPrint* platformPrint)
291 {
292 }
293
294 static void handleDraw(PluginObject *obj, NPCocoaEvent *event)
295 {
296     NSGraphicsContext *oldContext = [[NSGraphicsContext currentContext] retain];
297     
298     NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:event->data.draw.context flipped:YES];
299
300     [NSGraphicsContext setCurrentContext:context];
301     
302     NSRect rect = NSMakeRect(0, 0, obj.window.width, obj.window.height);
303     
304     [[NSColor lightGrayColor] set];
305     [NSBezierPath fillRect:rect];
306
307     // If the plugin has focus, draw a focus indicator
308     if (obj.hasFocus) {
309         [[NSColor blackColor] set];
310         NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect];
311         [path setLineWidth:5];
312         [path stroke];
313     }
314     
315     [obj.displayString drawAtPoint:NSMakePoint(10, 10) withAttributes:nil];
316     
317     [NSGraphicsContext setCurrentContext:oldContext];
318     [oldContext release];
319 }
320
321 static void invalidatePlugin(PluginObject *obj)
322 {
323     NPRect rect;
324     rect.left = 0;
325     rect.top = 0;
326     rect.right = obj.window.width;
327     rect.bottom = obj.window.height;
328     
329     browser->invalidaterect(obj.npp, &rect);
330 }
331
332
333 static void handleMouseEvent(PluginObject *obj, NPCocoaEvent *event)
334 {
335     if (event->data.mouse.buttonNumber == 1)
336         browser->popupcontextmenu(obj.npp, (NPNSMenu *)[obj.menuHandler menu]);
337 }
338
339 int16_t NPP_HandleEvent(NPP instance, void* event)
340 {
341     PluginObject *obj = instance->pdata;
342
343     NPCocoaEvent *cocoaEvent = event;
344     
345     switch (cocoaEvent->type) {
346     case NPCocoaEventFocusChanged:
347         obj.hasFocus = cocoaEvent->data.focus.hasFocus;
348         invalidatePlugin(obj);
349         return 1;
350         
351     case NPCocoaEventDrawRect:
352         handleDraw(obj, cocoaEvent);
353         return 1;
354     
355     case NPCocoaEventMouseDown:
356     case NPCocoaEventMouseUp:
357         
358     // FIXME: NPCocoaEventMouseMoved is currently disabled in order to see other events more clearly
359     // without "drowning" in mouse moved events.
360 //        case NPCocoaEventMouseMoved:
361     case NPCocoaEventMouseEntered:
362     case NPCocoaEventMouseExited:
363     case NPCocoaEventMouseDragged:
364     case NPCocoaEventScrollWheel:
365         handleMouseEvent(obj, cocoaEvent);
366         return 1;
367
368     default:
369         break;
370     }
371     
372     return 0;
373 }
374
375 void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
376 {
377
378 }
379
380 NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
381 {
382     PluginObject *obj = instance->pdata;
383
384     switch (variable) {
385     case NPPVpluginIsPlayingAudio:
386         if (value)
387             *((NPBool **)value) = obj.isPlayingAudio;
388         return NPERR_NO_ERROR;
389
390     default:
391         return NPERR_GENERIC_ERROR;
392     }
393 }
394
395 NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
396 {
397     PluginObject *obj = instance->pdata;
398
399     switch (variable) {
400     case NPNVmuteAudioBool:
401         if (value)
402             obj.muted = *((NPBool *)value);
403         return NPERR_NO_ERROR;
404
405     default:
406         return NPERR_GENERIC_ERROR;
407     }
408 }