1 /*****************************************************************************
2 * intf_macosx.c: MacOS X interface plugin
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
6 * Authors: Colin Delacroix <colin@zoy.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
21 *****************************************************************************/
23 #define MODULE_NAME macosx
24 #include "modules_inner.h"
26 /*****************************************************************************
28 *****************************************************************************/
31 #include <stdlib.h> /* malloc(), free() */
32 #include <sys/param.h> /* for MAXPATHLEN */
40 #include "interface.h"
42 #include "intf_playlist.h"
44 #include "stream_control.h"
45 #include "input_ext-intf.h"
47 #include "audio_output.h"
50 #include "video_output.h"
55 #include "macosx_common.h"
57 extern main_t *p_main;
60 /*****************************************************************************
62 *****************************************************************************/
64 //how often to have callback to main loop. Target of 30fps then 30hz + maybe some more...
65 //it doesn't really scale if we move to 2x the hz... something else is slowing us down...
66 #define kMainLoopFrequency (kEventDurationSecond / 45) //45 for good measure
76 kAppleQuit = 7, //is this always the same?
80 kFileCloseDivisor = 3,
83 kControlsPlayORPause = 1,
87 kControlsChapterDiv = 5,
88 kControlsChapterNext = 6,
89 kControlsChapterPrevious = 7,
92 kControlsVolumeDiv = 10,
93 kControlsVolumeUp = 11,
94 kControlsVolumeDown = 12,
95 kControlsVolumeMute = 13,
96 kControlsEjectDiv = 14,
105 kAppleQuit = 8, //is this always the same?
113 kControlsPlayORPause = 1,
118 kControlsChapterNext,
119 kControlsChapterPrevious,
130 //virtual key codes ; raw subtract 0x40 from these values
131 //http://devworld.apple.com/techpubs/mac/Text/Text-577.html#HEADING577-0
137 //http://devworld.apple.com/techpubs/mac/Text/Text-571.html#MARKER-9-18
138 kPeriod = 47, //(decimal)
139 kSpace = 49, //(decimal)
140 kEscape = 53 //(decimal)
145 // Initial Window Constants
148 kAboutWindowOffset = 200,
149 kAboutWindowWidth = 200, //400
150 kAboutWindowHeight = 50 //100
154 /*****************************************************************************
155 * intf_sys_t: description and status of the interface
156 *****************************************************************************/
157 typedef struct intf_sys_s
159 EventLoopTimerRef manageTimer;
161 WindowRef p_aboutWindow;
164 /*****************************************************************************
166 *****************************************************************************/
167 static int intf_Probe ( probedata_t *p_data );
168 static int intf_Open ( intf_thread_t *p_intf );
169 static void intf_Close ( intf_thread_t *p_intf );
170 static void intf_Run ( intf_thread_t *p_intf );
174 static int MakeAboutWindow ( intf_thread_t *p_intf );
176 void CarbonManageCallback ( EventLoopTimerRef inTimer, void *inUserData );
178 OSErr MyOpenDocument(const FSSpecPtr defaultLocationfssPtr);
180 void playorpause ( intf_thread_t *p_intf );
181 void stop ( intf_thread_t *p_intf );
185 void EventLoop( intf_thread_t *p_intf );
186 void DoEvent( intf_thread_t *p_intf , EventRecord *event);
187 void DoMenuCommand( intf_thread_t *p_intf , long menuResult);
188 void DrawWindow(WindowRef window);
189 void DrawAboutWindow(WindowRef window);
192 pascal OSErr QuitEventHandler(const AppleEvent *theEvent, AppleEvent *theReply, SInt32 refCon);
193 static pascal OSStatus MyKeyHandler( EventHandlerCallRef inCallRef, EventRef inEvent, void* userData );
194 static pascal OSStatus MyWindowEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData);
198 /*****************************************************************************
199 * Functions exported as capabilities. They are declared as static so that
200 * we don't pollute the namespace too much.
201 *****************************************************************************/
202 void _M( intf_getfunctions )( function_list_t * p_function_list )
204 p_function_list->pf_probe = intf_Probe;
205 p_function_list->functions.intf.pf_open = intf_Open;
206 p_function_list->functions.intf.pf_close = intf_Close;
207 p_function_list->functions.intf.pf_run = intf_Run;
210 /*****************************************************************************
211 * intf_Probe: probe the interface and return a score
212 *****************************************************************************
213 * This function checks the interface can be run and returns a score to the
214 * plugin manager so that it can select the best plugin.
215 *****************************************************************************/
216 static int intf_Probe( probedata_t *p_data )
218 if( TestMethod( INTF_METHOD_VAR, "macosx" ) )
223 /* Under MacOS X, this plugin always works */
227 /*****************************************************************************
228 * intf_Open: initialize interface
229 *****************************************************************************/
230 static int intf_Open( intf_thread_t *p_intf )
235 /* Allocate instance and initialize some members */
236 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
237 if( p_intf->p_sys == NULL )
244 SetQDGlobalsRandomSeed( TickCount() );
246 // neat menu but don't know if we want it.
247 // Install the Windows menu. Free of charge!
248 // CreateStandardWindowMenu( 0, &windMenu );
249 // InsertMenu( windMenu, 0 );
252 menu = NewMenu( kMenuApple, "\p\024" );
253 AppendMenu( menu, "\pAbout VLCÉ/A" );
254 InsertMenu( menu, 0 );
256 menu = NewMenu( kMenuFile, "\pFile" );
257 AppendMenu( menu, "\pNew Viewer Window/N" );
258 AppendMenu( menu, "\pOpenÉ/O" );
259 AppendMenu( menu, "\p(-" );
260 AppendMenu( menu, "\pClose/W" );
261 //standard OS X application menu quit isn't working nicely
262 AppendMenu( menu, "\pQuit/Q" );
263 InsertMenu( menu, 0 );
265 //BIG HONKING MENU - in order Mac OS 9 dvd player
266 //can't get key codes right for menus... argh that's why they use resources!
268 menu = NewMenu( kMenuControls, "\pControls" );
270 AppendMenu( menu, "\pPlay/," );
271 // SetMenuItemCommandKey(menu, 0, false, kSpace);
272 // SetMenuItemModifiers( menu, 0, kMenuNoCommandModifier);
274 AppendMenu( menu, "\pStop/." );
276 AppendMenu( menu, "\pFast Forward/f" );
277 // SetMenuItemCommandKey(menu, 2, false, kRightArrow);
279 AppendMenu( menu, "\pRewind/r" );
280 // SetMenuItemCommandKey(menu, 3, false, kLeftArrow);
282 AppendMenu( menu, "\p(-" ); //4
284 AppendMenu( menu, "\pNext Chapter/c" );
285 // SetMenuItemCommandKey(menu, 5, false, kRightArrow);
286 // SetMenuItemModifiers( menu, 5, kMenuNoCommandModifier);
288 AppendMenu( menu, "\pPrevious Chapter/p" );
289 // SetMenuItemCommandKey(menu, 6, false, kLeftArrow);
290 // SetMenuItemModifiers( menu, 6, kMenuNoCommandModifier);
292 AppendMenu( menu, "\p(-" ); //7
294 AppendMenu( menu, "\pDVD Menu/v" );
295 // SetMenuItemCommandKey(menu, 8, false, kEscape);
296 // SetMenuItemModifiers( menu, 8, kMenuNoCommandModifier);
298 AppendMenu( menu, "\p(-" ); //9
300 AppendMenu( menu, "\pVolume Up/u" );
301 // SetMenuItemCommandKey(menu, 10, false, kUpArrow);
303 AppendMenu( menu, "\pVolume Down/d" );
304 // SetMenuItemCommandKey(menu, 11, false, kDownArrow);
306 AppendMenu( menu, "\pMute/M" ); //12
308 AppendMenu( menu, "\p(-" ); //13
310 AppendMenu( menu, "\pEject/E" ); //14
312 InsertMenu( menu, 0 );
314 //Hmm, eventually we might want more than one player window, but for now we assume one only (like OS 9 player)
315 //and since we start with a window open, we temporarily disable the 'new' menu
316 DisableMenuItem( GetMenuHandle(kMenuFile), kFileNew);
318 //FIXME - Disabled Menus which are not implemented yet
319 DisableMenuItem( GetMenuHandle(kMenuControls), kControlsDVDMenu);
320 DisableMenuItem( GetMenuHandle(kMenuControls), kControlsEject);
324 if( MakeAboutWindow( p_intf ) )
326 intf_ErrMsg( "vout error: can't make about window" );
333 /*****************************************************************************
334 * intf_Close: destroy interface
335 *****************************************************************************/
336 static void intf_Close( intf_thread_t *p_intf )
338 /* Destroy structure */
339 free( p_intf->p_sys );
342 /*****************************************************************************
343 * intf_Run: main loop
344 *****************************************************************************/
345 static void intf_Run( intf_thread_t *p_intf )
348 EventLoopTimerUPP manageUPP;
351 Eventually we want to use Carbon events, or maybe even write this app in Cocoa
353 //kinda going out of bounds here... need to bring window creation to this file.
356 EventTypeSpec windowEventType = { kEventClassWindow, kEventWindowClose };
357 EventHandlerUPP windowHandlerUPP;
359 EventTypeSpec keyboardEventType = { kEventClassKeyboard, kEventRawKeyDown };
360 EventHandlerUPP keyboardHandlerUPP;
363 manageUPP = NewEventLoopTimerUPP ( CarbonManageCallback );
364 err = InstallEventLoopTimer ( GetCurrentEventLoop(), 0, kMainLoopFrequency, manageUPP, (void *) p_intf, &p_intf->p_sys->manageTimer );
365 assert(err == noErr);
366 DisposeEventLoopTimerUPP(manageUPP);
369 windowHandlerUPP = NewEventHandlerUPP ( MyWindowEventHandler );
370 err = InstallWindowEventHandler ( p_main->p_vout->p_sys->p_window , windowHandlerUPP, GetEventTypeCount(windowEventType), &windowEventType, (void *) p_intf, NULL );
371 assert(err == noErr);
372 DisposeEventHandlerUPP(windowHandlerUPP);
380 RunApplicationEventLoop();
382 err = RemoveEventLoopTimer(p_intf->p_sys->manageTimer);
383 assert(err == noErr);
387 /*****************************************************************************
388 * MakeAboutWindow: similar to MakeWindow in vout_macosx.c ;
389 * open and set-up a Mac OS window to be used for 'about' program...
390 * create it hidden and only show it when requested
391 *****************************************************************************/
392 static int MakeAboutWindow( intf_thread_t *p_intf )
396 int bottom = kAboutWindowHeight;
397 int right = kAboutWindowWidth;
399 WindowAttributes windowAttr = kWindowCloseBoxAttribute |
400 kWindowStandardHandlerAttribute |
401 kWindowInWindowMenuAttribute;
403 SetRect( &p_intf->p_sys->aboutRect, left, top, right, bottom );
404 OffsetRect( &p_intf->p_sys->aboutRect, kAboutWindowOffset, kAboutWindowOffset );
406 CreateNewWindow( kDocumentWindowClass, windowAttr, &p_intf->p_sys->aboutRect, &p_intf->p_sys->p_aboutWindow );
407 if ( p_intf->p_sys->p_aboutWindow == nil )
412 InstallStandardEventHandler(GetWindowEventTarget(p_intf->p_sys->p_aboutWindow));
413 SetWindowTitleWithCFString( p_intf->p_sys->p_aboutWindow, CFSTR("About DVD.app & VLC") );
419 void CarbonManageCallback ( EventLoopTimerRef inTimer, void *inUserData )
421 intf_thread_t * p_intf = (intf_thread_t *) inUserData;
423 /* Manage core vlc functions through the callback */
424 p_intf->pf_manage( p_intf );
428 QuitApplicationEventLoop();
434 void EventLoop( intf_thread_t *p_intf )
441 p_intf->pf_manage( p_intf );
442 gotEvent = WaitNextEvent(everyEvent,&event,32767,nil);
444 DoEvent( p_intf, &event);
445 } while (! p_intf->b_die );
451 void DoEvent( intf_thread_t *p_intf , EventRecord *event)
457 WindowRef whichWindow;
462 part = FindWindow(event->where, &whichWindow);
465 case inMenuBar: /* process a moused menu command */
466 DoMenuCommand( p_intf, MenuSelect(event->where));
473 if (whichWindow != FrontWindow())
474 SelectWindow(whichWindow);
477 case inDrag: /* pass screenBits.bounds */
478 GetRegionBounds(GetGrayRgn(), &tempRect);
479 DragWindow(whichWindow, event->where, &tempRect);
486 p_intf->b_die = true;
492 hit = TrackBox(whichWindow, event->where, part);
495 SetPort(GetWindowPort(whichWindow)); // window must be current port
496 EraseRect(GetWindowPortBounds(whichWindow, &tempRect)); // inval/erase because of ZoomWindow bug
497 ZoomWindow(whichWindow, part, true);
498 InvalWindowRect(whichWindow, GetWindowPortBounds(whichWindow, &tempRect));
506 key = event->message & charCodeMask;
507 if (event->modifiers & cmdKey)
508 if (event->what == keyDown)
509 DoMenuCommand( p_intf, MenuKey(key));
511 case activateEvt: /* if you needed to do something special */
515 DrawWindow((WindowRef) event->message);
518 case kHighLevelEvent:
519 AEProcessAppleEvent( event );
527 //the code for playorpause and stop taken almost directly from the BeOS code
528 void playorpause ( intf_thread_t *p_intf )
530 // pause the playback
531 if (p_intf->p_input != NULL )
533 // mute the volume if currently playing
534 if (p_main->p_vout->p_sys->playback_status == PLAYING)
536 if (p_main->p_aout != NULL)
538 p_main->p_aout->vol = 0;
540 p_main->p_vout->p_sys->playback_status = PAUSED;
541 SetMenuItemText( GetMenuHandle(kMenuControls), kControlsPlayORPause, "\pPlay");
544 // restore the volume
546 if (p_main->p_aout != NULL)
548 p_main->p_aout->vol = p_main->p_vout->p_sys->vol_val;
550 p_main->p_vout->p_sys->playback_status = PLAYING;
551 SetMenuItemText( GetMenuHandle(kMenuControls), kControlsPlayORPause, "\pPause");
554 input_SetStatus(p_intf->p_input, INPUT_STATUS_PAUSE);
558 void stop ( intf_thread_t *p_intf )
560 // this currently stops playback not nicely
561 if (p_intf->p_input != NULL )
563 // silence the sound, otherwise very horrible
564 if (p_main->p_aout != NULL)
566 p_main->p_aout->vol = 0;
569 input_SetStatus(p_intf->p_input, INPUT_STATUS_END);
571 p_main->p_vout->p_sys->playback_status = STOPPED;
575 void DoMenuCommand( intf_thread_t *p_intf , long menuResult)
577 short menuID; /* the resource ID of the selected menu */
578 short menuItem; /* the item number of the selected menu */
581 menuID = HiWord(menuResult); /* use macros to get item & menu number */
582 menuItem = LoWord(menuResult);
590 ShowWindow( p_intf->p_sys->p_aboutWindow );
591 SelectWindow( p_intf->p_sys->p_aboutWindow );
592 DrawAboutWindow( p_intf->p_sys->p_aboutWindow); //kludge
593 EnableMenuItem( GetMenuHandle(kMenuFile), kFileClose);
597 p_intf->b_die = true;
598 //hrmm... don't know what is going on w/ the Quit item in the new application menu...documentation???
610 ShowWindow( p_main->p_vout->p_sys->p_window );
611 SelectWindow( p_main->p_vout->p_sys->p_window );
612 DisableMenuItem( GetMenuHandle(kMenuFile), kFileNew);
613 EnableMenuItem( GetMenuHandle(kMenuFile), kFileClose);
614 //hmm, can't say to play() right now because I don't know if a file is in playlist yet.
615 //need to see if I can tell this or eve if calling play() w/o a file is bad...not sure of either
619 playorpause( p_intf );
621 // starts playing automatically on open? playorpause( p_intf );
625 HideWindow( FrontWindow() );
626 if ( ! IsWindowVisible( p_main->p_vout->p_sys->p_window ) && ! IsWindowVisible( p_intf->p_sys->p_aboutWindow ) )
628 //calling this even if no file open shouldn't be bad... not sure of opposite situation above
630 EnableMenuItem( GetMenuHandle(kMenuFile), kFileNew);
631 DisableMenuItem( GetMenuHandle(kMenuFile), kFileClose);
637 p_intf->b_die = true;
648 case kControlsPlayORPause:
649 playorpause( p_intf );
656 case kControlsForward:
657 // cycle the fast playback modes
658 if (p_intf->p_input != NULL )
660 if (p_main->p_aout != NULL)
662 p_main->p_aout->vol = 0;
665 input_SetStatus(p_intf->p_input, INPUT_STATUS_FASTER);
669 case kControlsRewind:
670 // cycle the slow playback modes
671 if (p_intf->p_input != NULL )
673 if (p_main->p_aout != NULL)
675 p_main->p_aout->vol = 0;
678 input_SetStatus(p_intf->p_input, INPUT_STATUS_SLOWER);
682 case kControlsChapterNext:
683 if( p_intf->p_input != NULL )
685 /* FIXME: temporary hack */
686 p_intf->p_input->b_eof = 1;
690 case kControlsChapterPrevious:
691 if( p_intf->p_input != NULL )
693 /* FIXME: temporary hack */
694 intf_PlaylistPrev( p_main->p_playlist );
695 intf_PlaylistPrev( p_main->p_playlist );
696 p_intf->p_input->b_eof = 1;
700 case kControlsDVDMenu:
705 case kControlsVolumeUp:
707 if (p_main->p_aout != NULL)
709 p_main->p_aout->vol++;
713 case kControlsVolumeDown:
715 if (p_main->p_aout != NULL)
717 p_main->p_aout->vol--;
721 case kControlsVolumeMute:
723 if (p_main->p_aout != NULL)
725 if (p_main->p_aout->vol == 0)
727 //p_vol->SetEnabled(true);
728 p_main->p_aout->vol = p_main->p_vout->p_sys->vol_val;
732 //p_vol->SetEnabled(false);
733 p_main->p_vout->p_sys->vol_val = p_main->p_aout->vol;
734 p_main->p_aout->vol = 0;
752 HiliteMenu(0); /* unhighlight what MenuSelect (or MenuKey) hilited */
755 void DrawWindow(WindowRef window)
758 GrafPtr previousPort;
760 GetPort(&previousPort);
761 SetPort(GetWindowPort(window));
763 EraseRect(GetWindowPortBounds(window, &tempRect));
764 DrawControls(window);
765 DrawGrowIcon(window);
767 SetPort(previousPort);
770 void DrawAboutWindow(WindowRef window)
772 GrafPtr previousPort;
774 GetPort(&previousPort);
775 SetPort(GetWindowPort(window));
778 DrawString("\phttp://www.videolan.org");
780 SetPort(previousPort);
785 static pascal OSStatus MyEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
790 HICommand commandStruct;
793 OSStatus result = eventNotHandledErr; // report failure by default
795 GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(window), NULL, &window);
797 whatHappened = GetEventKind(event);
799 switch (whatHappened)
801 case kEventWindowActivated:
804 case kEventWindowDeactivated:
807 case kEventWindowDrawContent:
812 case kEventWindowBoundsChanged:
813 InvalWindowRect(window, GetWindowPortBounds(window, &bounds));
818 case kEventWindowClickContentRgn:
819 /*DoContentClick(window);
825 case kEventCommandProcess:
826 GetEventParameter (event, kEventParamDirectObject,
827 typeHICommand, NULL, sizeof(HICommand),
828 NULL, &commandStruct);
829 theMenuRef = commandStruct.menu.menuRef;
831 if (theMenuRef == GetMenuHandle(kMenuApple))
833 // Because the event didn't occur *in* the window, the
834 // window reference isn't valid until we set it here
835 window = FrontWindow();
837 theMenuItem = commandStruct.menu.menuItemIndex;
838 switch ( theMenuItem )
841 SetLight(window, true);
844 SetLight(window, false);
854 case kEventMouseMoved:
856 CursorRgn = NewRgn();
857 GetEventParameter (event, kEventParamMouseLocation, typeQDPoint,
858 NULL, sizeof(Point), NULL, &wheresMyMouse);
859 AdjustCursor(wheresMyMouse, CursorRgn);
860 DisposeRgn(CursorRgn);
866 // If nobody handled the event, it gets propagated to the
867 // application-level handler.
874 //FIXME Adding this has introduced or surfaced a lot of bugs...
875 //comented out a lot of things to strip this down to make this a 'quicky'
876 OSErr MyOpenDocument(const FSSpecPtr defaultLocationfssPtr)
878 NavDialogOptions dialogOptions;
879 // AEDesc defaultLocation;
880 // NavEventUPP eventProc = NewNavEventProc(myEventProc);
881 // NavObjectFilterUPP filterProc =
882 // NewNavObjectFilterProc(myFilterProc);
885 // Specify default options for dialog box
886 anErr = NavGetDefaultDialogOptions(&dialogOptions);
889 // Adjust the options to fit our needs
890 // Set default location option
891 // dialogOptions.dialogOptionFlags |= kNavSelectDefaultLocation;
892 // Clear preview option
893 dialogOptions.dialogOptionFlags ^= kNavAllowPreviews;
895 // make descriptor for default location
896 // anErr = AECreateDesc(typeFSS, defaultLocationfssPtr,
897 // sizeof(*defaultLocationfssPtr),
898 // &defaultLocation );
901 // Get 'open' resource. A nil handle being returned is OK,
902 // this simply means no automatic file filtering.
903 NavTypeListHandle typeList = (NavTypeListHandle)GetResource(
905 NavReplyRecord reply;
907 // Call NavGetFile() with specified options and
908 // declare our app-defined functions and type list
909 // anErr = NavGetFile (&defaultLocation, &reply, &dialogOptions,
910 anErr = NavGetFile (nil, &reply, &dialogOptions,
911 // eventProc, nil, filterProc,
914 if (anErr == noErr && reply.validRecord)
916 // Deal with multiple file selection
919 anErr = AECountItems(&(reply.selection), &count);
920 // Set up index for file list
925 for (index = 1; index <= count; index++)
927 AEKeyword theKeyword;
930 FSSpec documentFSSpec;
932 // Get a pointer to selected file
933 anErr = AEGetNthPtr(&(reply.selection), index,
934 typeFSS, &theKeyword,
935 &actualType,&documentFSSpec,
936 sizeof(documentFSSpec),
940 // anErr = DoOpenFile(&documentFSSpec);
943 char path[MAXPATHLEN];
945 //make an FSRef out of an FSSpec
946 anErr = FSpMakeFSRef( &documentFSSpec, &newRef);
951 //make a path out of the FSRef
952 anErr = FSRefMakePath( &newRef, path, MAXPATHLEN);
958 //else, ok...add it to playlist!
959 intf_PlaylistAdd( p_main->p_playlist, PLAYLIST_END, path );
965 // Dispose of NavReplyRecord, resources, descriptors
966 anErr = NavDisposeReply(&reply);
968 if (typeList != NULL)
970 ReleaseResource( (Handle)typeList);
972 //(void) AEDisposeDesc(&defaultLocation);
975 // DisposeRoutineDescriptor(eventProc);
976 // DisposeRoutineDescriptor(filterProc);