12296
|
1 /*
|
|
2 vo_quartz.c
|
|
3
|
|
4 by Nicolas Plourde <nicolasplourde@hotmail.com>
|
|
5
|
|
6 Copyright (c) Nicolas Plourde - April 2004
|
12414
|
7
|
|
8 YUV support Copyright (C) 2004 Romain Dolbeau <romain@dolbeau.org>
|
12296
|
9
|
|
10 MPlayer Mac OSX Quartz video out module.
|
|
11
|
12432
|
12 todo: -'plist' resource
|
|
13 -Choose fullscreen display device (-xineramascreen / -multiscreen).
|
12296
|
14 -resize black bar without CGContext
|
|
15 -rootwin
|
12460
|
16 -screen overlay output
|
12296
|
17 -non-blocking event
|
|
18 -(add sugestion here)
|
12120
|
19 */
|
|
20
|
|
21 //SYS
|
|
22 #include <stdio.h>
|
|
23
|
|
24 //OSX
|
|
25 #include <Carbon/Carbon.h>
|
12414
|
26 #include <QuickTime/QuickTime.h>
|
12120
|
27
|
|
28 //MPLAYER
|
|
29 #include "config.h"
|
12414
|
30 #include "fastmemcpy.h"
|
12120
|
31 #include "video_out.h"
|
|
32 #include "video_out_internal.h"
|
|
33 #include "aspect.h"
|
12414
|
34 #include "mp_msg.h"
|
|
35 #include "m_option.h"
|
12120
|
36
|
|
37 #include "../input/input.h"
|
|
38 #include "../input/mouse.h"
|
|
39
|
|
40 #include "vo_quartz.h"
|
|
41
|
12296
|
42 static vo_info_t info =
|
|
43 {
|
|
44 "Mac OSX (Quartz)",
|
|
45 "quartz",
|
12414
|
46 "Nicolas Plourde <nicolasplourde@hotmail.com>, Romain Dolbeau <romain@dolbeau.org>",
|
12296
|
47 ""
|
12120
|
48 };
|
|
49
|
12296
|
50 LIBVO_EXTERN(quartz)
|
12120
|
51
|
12414
|
52 static uint32_t image_depth;
|
|
53 static uint32_t image_format;
|
|
54 static uint32_t image_size;
|
|
55 static uint32_t image_buffer_size;
|
12432
|
56 static char *image_data;
|
12414
|
57
|
12432
|
58 static ImageSequence seqId;
|
12414
|
59 static CodecType image_qtcodec;
|
12487
|
60 static PlanarPixmapInfoYUV420 *P = NULL;
|
12424
|
61 static struct
|
|
62 {
|
|
63 ImageDescriptionHandle desc;
|
|
64 Handle extension_colr;
|
|
65 Handle extension_fiel;
|
|
66 Handle extension_clap;
|
|
67 Handle extension_pasp;
|
12414
|
68 } yuv_qt_stuff;
|
12432
|
69 static MatrixRecord matrix;
|
12414
|
70 static int EnterMoviesDone = 0;
|
12487
|
71 static int get_image_done = 0;
|
12120
|
72
|
12296
|
73 extern int vo_ontop;
|
12487
|
74 extern int vo_fs; // user want fullscreen
|
|
75 static int vo_quartz_fs; // we are in fullscreen
|
12120
|
76
|
12487
|
77 static int int_pause = 0;
|
|
78 static float winAlpha = 1;
|
12296
|
79
|
12487
|
80 static int device_width;
|
|
81 static int device_height;
|
12120
|
82
|
12487
|
83 static WindowRef theWindow = NULL;
|
12296
|
84
|
12487
|
85 static Rect imgRect; // size of the original image (unscaled)
|
|
86 static Rect dstRect; // size of the displayed image (after scaling)
|
|
87 static Rect winRect; // size of the window containg the displayed image (include padding)
|
|
88 static Rect oldWinRect; // size of the window containg the displayed image (include padding) when NOT in FS mode
|
12296
|
89
|
12487
|
90 static CGContextRef context;
|
12120
|
91
|
|
92 #include "../osdep/keycodes.h"
|
12296
|
93 extern void mplayer_put_key(int code);
|
|
94
|
|
95 extern void vo_draw_text(int dxs,int dys,void (*draw_alpha)(int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride));
|
12120
|
96
|
|
97 //PROTOTYPE/////////////////////////////////////////////////////////////////
|
12296
|
98 void window_resized();
|
|
99 void window_ontop();
|
|
100 void window_fullscreen();
|
12120
|
101
|
12460
|
102 static OSStatus MainEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
|
12296
|
103
|
|
104 static void draw_alpha(int x0, int y0, int w, int h, unsigned char *src, unsigned char *srca, int stride)
|
|
105 {
|
12424
|
106 switch (image_format)
|
|
107 {
|
|
108 case IMGFMT_RGB32:
|
|
109 vo_draw_alpha_rgb32(w,h,src,srca,stride,image_data+4*(y0*imgRect.right+x0),4*imgRect.right);
|
|
110 break;
|
|
111 case IMGFMT_YV12:
|
|
112 case IMGFMT_IYUV:
|
|
113 case IMGFMT_I420:
|
12487
|
114 vo_draw_alpha_yv12(w,h,src,srca,stride, ((char*)P) + P->componentInfoY.offset + x0 + y0 * imgRect.right, imgRect.right);
|
12424
|
115 break;
|
|
116 case IMGFMT_UYVY:
|
12487
|
117 //vo_draw_alpha_uyvy(w,h,src,srca,stride,((char*)P) + (x0 + y0 * imgRect.right) * 2,imgRect.right*2);
|
12424
|
118 break;
|
|
119 case IMGFMT_YUY2:
|
12487
|
120 vo_draw_alpha_yuy2(w,h,src,srca,stride,((char*)P) + (x0 + y0 * imgRect.right) * 2,imgRect.right*2);
|
12424
|
121 break;
|
12487
|
122 }
|
12296
|
123 }
|
12120
|
124
|
|
125 //default window event handler
|
12460
|
126 static OSStatus MainEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
|
12120
|
127 {
|
12296
|
128 OSStatus err = noErr;
|
12460
|
129 OSStatus result = eventNotHandledErr;
|
|
130 UInt32 class = GetEventClass (event);
|
|
131 UInt32 kind = GetEventKind (event);
|
|
132
|
|
133 if(class == kEventClassWindow)
|
|
134 {
|
|
135 WindowRef window;
|
|
136 Rect rectPort = {0,0,0,0};
|
|
137
|
|
138 GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &window);
|
|
139
|
|
140 if(window)
|
|
141 {
|
|
142 GetWindowPortBounds (window, &rectPort);
|
|
143 }
|
12296
|
144
|
12460
|
145 switch (kind)
|
|
146 {
|
|
147 //close window
|
|
148 case kEventWindowClosed:
|
|
149 mplayer_put_key(KEY_ESC);
|
|
150 break;
|
|
151
|
|
152 //resize window
|
|
153 case kEventWindowBoundsChanged:
|
|
154 window_resized();
|
|
155 flip_page();
|
|
156 break;
|
|
157
|
|
158 default:
|
|
159 err = eventNotHandledErr;
|
|
160 break;
|
|
161 }
|
|
162 }
|
|
163 else if(class == kEventClassKeyboard)
|
|
164 {
|
|
165 char macCharCodes;
|
|
166 UInt32 macKeyCode;
|
|
167 UInt32 macKeyModifiers;
|
|
168
|
|
169 GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(macCharCodes), NULL, &macCharCodes);
|
|
170 GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(macKeyCode), NULL, &macKeyCode);
|
|
171 GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(macKeyModifiers), NULL, &macKeyModifiers);
|
12296
|
172
|
12460
|
173 switch (kind)
|
12296
|
174 {
|
12460
|
175 case kEventRawKeyDown:
|
|
176 {
|
|
177 switch(macKeyCode)
|
|
178 {
|
|
179 case QZ_RETURN: mplayer_put_key(KEY_ENTER);break;
|
|
180 case QZ_ESCAPE: mplayer_put_key(KEY_ESC);break;
|
|
181 case QZ_F1: mplayer_put_key(KEY_F+1);break;
|
|
182 case QZ_F2: mplayer_put_key(KEY_F+2);break;
|
|
183 case QZ_F3: mplayer_put_key(KEY_F+3);break;
|
|
184 case QZ_F4: mplayer_put_key(KEY_F+4);break;
|
|
185 case QZ_F5: mplayer_put_key(KEY_F+5);break;
|
|
186 case QZ_F6: mplayer_put_key(KEY_F+6);break;
|
|
187 case QZ_F7: mplayer_put_key(KEY_F+7);break;
|
|
188 case QZ_F8: mplayer_put_key(KEY_F+8);break;
|
|
189 case QZ_F9: mplayer_put_key(KEY_F+9);break;
|
|
190 case QZ_F10: mplayer_put_key(KEY_F+10);break;
|
|
191 case QZ_F11: mplayer_put_key(KEY_F+11);break;
|
|
192 case QZ_F12: mplayer_put_key(KEY_F+12);break;
|
|
193 //case QZ_7: mplayer_put_key(shift_key?'/':'7');
|
|
194 //case QZ_PLUS: mplayer_put_key(shift_key?'*':'+');
|
|
195 case QZ_KP_PLUS: mplayer_put_key('+');break;
|
|
196 case QZ_MINUS:
|
|
197 case QZ_KP_MINUS: mplayer_put_key('-');break;
|
|
198 case QZ_TAB: mplayer_put_key('\t');break;
|
|
199 case QZ_PAGEUP: mplayer_put_key(KEY_PAGE_UP);break;
|
|
200 case QZ_PAGEDOWN: mplayer_put_key(KEY_PAGE_DOWN);break;
|
|
201 case QZ_UP: mplayer_put_key(KEY_UP);break;
|
|
202 case QZ_DOWN: mplayer_put_key(KEY_DOWN);break;
|
|
203 case QZ_LEFT: mplayer_put_key(KEY_LEFT);break;
|
|
204 case QZ_RIGHT: mplayer_put_key(KEY_RIGHT);break;
|
|
205 case QZ_KP_MULTIPLY: mplayer_put_key('*'); break;
|
|
206 case QZ_SLASH:
|
|
207 case QZ_KP_DIVIDE: mplayer_put_key('/'); break;
|
|
208 case QZ_KP0: mplayer_put_key(KEY_KP0); break;
|
|
209 case QZ_KP1: mplayer_put_key(KEY_KP1); break;
|
|
210 case QZ_KP2: mplayer_put_key(KEY_KP2); break;
|
|
211 case QZ_KP3: mplayer_put_key(KEY_KP3); break;
|
|
212 case QZ_KP4: mplayer_put_key(KEY_KP4); break;
|
|
213 case QZ_KP5: mplayer_put_key(KEY_KP5); break;
|
|
214 case QZ_KP6: mplayer_put_key(KEY_KP6); break;
|
|
215 case QZ_KP7: mplayer_put_key(KEY_KP7); break;
|
|
216 case QZ_KP8: mplayer_put_key(KEY_KP8); break;
|
|
217 case QZ_KP9: mplayer_put_key(KEY_KP9); break;
|
|
218 case QZ_KP_PERIOD: mplayer_put_key(KEY_KPDEC); break;
|
|
219 case QZ_KP_ENTER: mplayer_put_key(KEY_KPENTER); break;
|
|
220 case QZ_LEFTBRACKET: SetWindowAlpha(theWindow, winAlpha-=0.05);break;
|
|
221 case QZ_RIGHTBRACKET: SetWindowAlpha(theWindow, winAlpha+=0.05);break;
|
|
222
|
|
223 default:mplayer_put_key(macCharCodes);break;
|
|
224 }
|
|
225 }
|
|
226
|
|
227 default:
|
|
228 err = eventNotHandledErr;
|
|
229 break;
|
|
230 }
|
|
231 }
|
|
232 else if(class == kEventClassMouse)
|
|
233 {
|
|
234 WindowPtr tmpWin;
|
|
235 Point mousePos;
|
|
236
|
|
237 GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, 0, sizeof(Point), 0, &mousePos);
|
|
238
|
|
239 switch (kind)
|
|
240 {
|
|
241 case kEventMouseDown:
|
12296
|
242 {
|
12460
|
243 EventMouseButton button;
|
|
244 GetEventParameter(event, kEventParamMouseButton, typeMouseButton, 0, sizeof(EventMouseButton), 0, &button);
|
|
245
|
12296
|
246 short part = FindWindow(mousePos,&tmpWin);
|
|
247
|
|
248 if(part == inMenuBar)
|
|
249 {
|
|
250 MenuSelect(mousePos);
|
12460
|
251 HiliteMenu(0);
|
12296
|
252 }
|
12460
|
253 else if(part == inContent)
|
|
254 {
|
|
255 switch(button)
|
|
256 {
|
|
257 case 1: mplayer_put_key(MOUSE_BTN0);break;
|
|
258 case 2: mplayer_put_key(MOUSE_BTN2);break;
|
|
259 case 3: mplayer_put_key(MOUSE_BTN1);break;
|
|
260
|
|
261 default:break;
|
|
262 }
|
|
263 }
|
|
264 }
|
|
265 break;
|
|
266
|
|
267 case kEventMouseWheelMoved:
|
|
268 {
|
|
269 int wheel;
|
|
270 GetEventParameter(event, kEventParamMouseWheelDelta, typeSInt32, 0, sizeof(int), 0, &wheel);
|
|
271
|
|
272 short part = FindWindow(mousePos,&tmpWin);
|
|
273
|
|
274 if(part == inContent)
|
|
275 {
|
|
276 if(wheel > 0)
|
|
277 mplayer_put_key(MOUSE_BTN3);
|
|
278 else
|
|
279 mplayer_put_key(MOUSE_BTN4);
|
|
280 }
|
|
281 }
|
|
282 break;
|
|
283
|
|
284 default:
|
|
285 err = eventNotHandledErr;
|
|
286 break;
|
|
287 }
|
|
288 }
|
|
289
|
12296
|
290 return err;
|
|
291 }
|
12120
|
292
|
12487
|
293 static void quartz_CreateWindow(uint32_t d_width, uint32_t d_height, WindowAttributes windowAttrs)
|
|
294 {
|
|
295 CFStringRef titleKey;
|
|
296 CFStringRef windowTitle;
|
|
297 OSStatus result;
|
|
298
|
|
299 SetRect(&winRect, 0, 0, d_width, d_height);
|
|
300 SetRect(&oldWinRect, 0, 0, d_width, d_height);
|
|
301 SetRect(&dstRect, 0, 0, d_width, d_height);
|
|
302
|
|
303 CreateNewWindow(kDocumentWindowClass, windowAttrs, &winRect, &theWindow);
|
|
304
|
|
305 //Set window title
|
|
306 titleKey = CFSTR("MPlayer");
|
|
307 windowTitle = CFCopyLocalizedString(titleKey, NULL);
|
|
308 result = SetWindowTitleWithCFString(theWindow, windowTitle);
|
|
309 CFRelease(titleKey);
|
|
310 CFRelease(windowTitle);
|
|
311
|
|
312 //Install event handler
|
|
313 const EventTypeSpec winEvents[] = { { kEventClassKeyboard, kEventRawKeyDown },
|
|
314 { kEventClassMouse, kEventMouseDown },
|
|
315 { kEventClassMouse, kEventMouseWheelMoved },
|
|
316 { kEventClassWindow, kEventWindowClosed },
|
|
317 { kEventClassWindow, kEventWindowBoundsChanged } };
|
|
318
|
|
319 InstallApplicationEventHandler (NewEventHandlerUPP (MainEventHandler), GetEventTypeCount(winEvents), winEvents, 0, NULL);
|
|
320 }
|
|
321
|
12296
|
322 static uint32_t config(uint32_t width, uint32_t height, uint32_t d_width, uint32_t d_height, uint32_t flags, char *title, uint32_t format)
|
|
323 {
|
|
324 WindowAttributes windowAttrs;
|
|
325 GDHandle deviceHdl;
|
|
326 Rect deviceRect;
|
12414
|
327 OSErr qterr;
|
12296
|
328
|
|
329 //Get Main device info///////////////////////////////////////////////////
|
|
330 deviceHdl = GetMainDevice();
|
|
331 deviceRect = (*deviceHdl)->gdRect;
|
|
332
|
|
333 device_width = deviceRect.right;
|
|
334 device_height = deviceRect.bottom;
|
|
335
|
|
336 //misc mplayer setup/////////////////////////////////////////////////////
|
12487
|
337 SetRect(&imgRect, 0, 0, width, height);
|
12414
|
338 switch (image_format)
|
|
339 {
|
|
340 case IMGFMT_RGB32:
|
12432
|
341 image_depth = 32;
|
12414
|
342 break;
|
|
343 case IMGFMT_YV12:
|
|
344 case IMGFMT_IYUV:
|
|
345 case IMGFMT_I420:
|
|
346 case IMGFMT_UYVY:
|
|
347 case IMGFMT_YUY2:
|
|
348 image_depth = 16;
|
|
349 break;
|
|
350 }
|
12487
|
351 image_size = ((imgRect.right*imgRect.bottom*image_depth)+7)/8;
|
12296
|
352
|
|
353 vo_fs = flags & VOFLAG_FULLSCREEN;
|
|
354
|
|
355 //get movie aspect
|
|
356 aspect_save_orig(width,height);
|
|
357 aspect_save_prescale(d_width,d_height);
|
|
358 aspect_save_screenres(device_width, device_height);
|
|
359
|
|
360 aspect(&d_width,&d_height,A_NOZOOM);
|
|
361
|
|
362 //Create player window//////////////////////////////////////////////////
|
|
363 windowAttrs = kWindowStandardDocumentAttributes
|
|
364 | kWindowStandardHandlerAttribute
|
|
365 | kWindowLiveResizeAttribute;
|
12425
|
366
|
12432
|
367 windowAttrs &= (~kWindowResizableAttribute);
|
12296
|
368
|
12487
|
369 if (theWindow == NULL)
|
|
370 {
|
|
371 quartz_CreateWindow(d_width, d_height, windowAttrs);
|
|
372
|
|
373 if (theWindow == NULL)
|
|
374 {
|
|
375 mp_msg(MSGT_VO, MSGL_FATAL, "Quartz error: Couldn't create window !!!!!\n");
|
|
376 return -1;
|
|
377 }
|
|
378 }
|
|
379 else
|
|
380 {
|
|
381 HideWindow(theWindow);
|
|
382 ChangeWindowAttributes(theWindow, ~windowAttrs, windowAttrs);
|
|
383 SetRect(&winRect, 0, 0, d_width, d_height);
|
|
384 SetRect(&oldWinRect, 0, 0, d_width, d_height);
|
|
385 SizeWindow (theWindow, d_width, d_height, 1);
|
|
386 }
|
|
387
|
|
388 get_image_done = 0;
|
12296
|
389
|
12432
|
390 if (!EnterMoviesDone)
|
|
391 {
|
|
392 qterr = EnterMovies();
|
|
393 EnterMoviesDone = 1;
|
|
394 }
|
|
395 else
|
|
396 qterr = 0;
|
|
397
|
|
398 if (qterr)
|
12424
|
399 {
|
12432
|
400 mp_msg(MSGT_VO, MSGL_FATAL, "Quartz error: EnterMovies (%d)\n", qterr);
|
12487
|
401 return -1;
|
12432
|
402 }
|
|
403
|
|
404 SetPort(GetWindowPort(theWindow));
|
|
405 SetIdentityMatrix(&matrix);
|
|
406
|
|
407 if ((d_width != width) || (d_height != height))
|
|
408 {
|
|
409 ScaleMatrix(&matrix, FixDiv(Long2Fix(d_width),Long2Fix(width)), FixDiv(Long2Fix(d_height),Long2Fix(height)), 0, 0);
|
|
410 }
|
12433
|
411
|
12432
|
412 switch (image_format)
|
|
413 {
|
|
414 case IMGFMT_RGB32:
|
12424
|
415 {
|
12432
|
416 ImageDescriptionHandle desc;
|
12487
|
417 GWorldPtr imgGWorld;
|
12432
|
418 image_data = calloc(sizeof(image_size),1);
|
12487
|
419 NewGWorldFromPtr (&imgGWorld, k32ARGBPixelFormat, &imgRect, 0, 0, 0, image_data, imgRect.right * 4);
|
12432
|
420 MakeImageDescriptionForPixMap(GetGWorldPixMap(imgGWorld), &desc);
|
|
421 DisposeGWorld(imgGWorld);
|
12424
|
422
|
12432
|
423 qterr = DecompressSequenceBeginS ( &seqId,
|
|
424 desc,
|
|
425 image_data,
|
|
426 image_size,
|
|
427 GetWindowPort(theWindow),
|
|
428 NULL,
|
|
429 NULL,
|
|
430 ((d_width != width) || (d_height != height)) ? &matrix : NULL,
|
|
431 srcCopy,
|
|
432 NULL,
|
|
433 0,
|
|
434 codecLosslessQuality,
|
|
435 bestSpeedCodec);
|
12487
|
436 free(image_data);
|
|
437 image_data = NULL;
|
12424
|
438 if (qterr)
|
|
439 {
|
12432
|
440 mp_msg(MSGT_VO, MSGL_FATAL, "Quartz error: DecompressSequenceBeginS (%d)\n", qterr);
|
12487
|
441 return -1;
|
12424
|
442 }
|
|
443 }
|
12432
|
444 break;
|
12424
|
445
|
12432
|
446 case IMGFMT_YV12:
|
|
447 case IMGFMT_IYUV:
|
|
448 case IMGFMT_I420:
|
|
449 case IMGFMT_UYVY:
|
|
450 case IMGFMT_YUY2:
|
12424
|
451 {
|
12432
|
452 yuv_qt_stuff.desc = (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
|
12424
|
453
|
12432
|
454 yuv_qt_stuff.extension_colr = NewHandleClear(sizeof(NCLCColorInfoImageDescriptionExtension));
|
|
455 ((NCLCColorInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_colr))->colorParamType = kVideoColorInfoImageDescriptionExtensionType;
|
|
456 ((NCLCColorInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_colr))->primaries = 2;
|
|
457 ((NCLCColorInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_colr))->transferFunction = 2;
|
|
458 ((NCLCColorInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_colr))->matrix = 2;
|
|
459
|
|
460 yuv_qt_stuff.extension_fiel = NewHandleClear(sizeof(FieldInfoImageDescriptionExtension));
|
|
461 ((FieldInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_fiel))->fieldCount = 1;
|
|
462 ((FieldInfoImageDescriptionExtension*)(*yuv_qt_stuff.extension_fiel))->fieldOrderings = 0;
|
12424
|
463
|
12432
|
464 yuv_qt_stuff.extension_clap = NewHandleClear(sizeof(CleanApertureImageDescriptionExtension));
|
12487
|
465 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->cleanApertureWidthN = imgRect.right;
|
12432
|
466 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->cleanApertureWidthD = 1;
|
12487
|
467 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->cleanApertureHeightN = imgRect.bottom;
|
12432
|
468 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->cleanApertureHeightD = 1;
|
|
469 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->horizOffN = 0;
|
|
470 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->horizOffD = 1;
|
|
471 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->vertOffN = 0;
|
|
472 ((CleanApertureImageDescriptionExtension*)(*yuv_qt_stuff.extension_clap))->vertOffD = 1;
|
|
473
|
|
474 yuv_qt_stuff.extension_pasp = NewHandleClear(sizeof(PixelAspectRatioImageDescriptionExtension));
|
|
475 ((PixelAspectRatioImageDescriptionExtension*)(*yuv_qt_stuff.extension_pasp))->hSpacing = 1;
|
|
476 ((PixelAspectRatioImageDescriptionExtension*)(*yuv_qt_stuff.extension_pasp))->vSpacing = 1;
|
|
477
|
|
478 (*yuv_qt_stuff.desc)->idSize = sizeof(ImageDescription);
|
|
479 (*yuv_qt_stuff.desc)->cType = image_qtcodec;
|
|
480 (*yuv_qt_stuff.desc)->version = 2;
|
|
481 (*yuv_qt_stuff.desc)->revisionLevel = 0;
|
|
482 (*yuv_qt_stuff.desc)->vendor = 'mpla';
|
12487
|
483 (*yuv_qt_stuff.desc)->width = imgRect.right;
|
|
484 (*yuv_qt_stuff.desc)->height = imgRect.bottom;
|
12432
|
485 (*yuv_qt_stuff.desc)->hRes = Long2Fix(72);
|
|
486 (*yuv_qt_stuff.desc)->vRes = Long2Fix(72);
|
|
487 (*yuv_qt_stuff.desc)->temporalQuality = 0;
|
|
488 (*yuv_qt_stuff.desc)->spatialQuality = codecLosslessQuality;
|
|
489 (*yuv_qt_stuff.desc)->frameCount = 1;
|
|
490 (*yuv_qt_stuff.desc)->dataSize = 0;
|
|
491 (*yuv_qt_stuff.desc)->depth = 24;
|
|
492 (*yuv_qt_stuff.desc)->clutID = -1;
|
12424
|
493
|
12432
|
494 qterr = AddImageDescriptionExtension(yuv_qt_stuff.desc, yuv_qt_stuff.extension_colr, kColorInfoImageDescriptionExtension);
|
|
495 if (qterr)
|
|
496 {
|
12487
|
497 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: AddImageDescriptionExtension [colr] (%d)\n", qterr);
|
12432
|
498 }
|
|
499
|
|
500 qterr = AddImageDescriptionExtension(yuv_qt_stuff.desc, yuv_qt_stuff.extension_fiel, kFieldInfoImageDescriptionExtension);
|
|
501 if (qterr)
|
|
502 {
|
12487
|
503 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: AddImageDescriptionExtension [fiel] (%d)\n", qterr);
|
12432
|
504 }
|
|
505
|
|
506 qterr = AddImageDescriptionExtension(yuv_qt_stuff.desc, yuv_qt_stuff.extension_clap, kCleanApertureImageDescriptionExtension);
|
|
507 if (qterr)
|
|
508 {
|
12487
|
509 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: AddImageDescriptionExtension [clap] (%d)\n", qterr);
|
12432
|
510 }
|
|
511
|
|
512 qterr = AddImageDescriptionExtension(yuv_qt_stuff.desc, yuv_qt_stuff.extension_pasp, kCleanApertureImageDescriptionExtension);
|
|
513 if (qterr)
|
|
514 {
|
12487
|
515 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: AddImageDescriptionExtension [pasp] (%d)\n", qterr);
|
12432
|
516 }
|
12487
|
517 if (P != NULL) { // second or subsequent movie
|
|
518 free(P);
|
|
519 }
|
12432
|
520 P = calloc(sizeof(PlanarPixmapInfoYUV420) + image_size, 1);
|
|
521 switch (image_format)
|
|
522 {
|
|
523 case IMGFMT_YV12:
|
|
524 case IMGFMT_IYUV:
|
|
525 case IMGFMT_I420:
|
|
526 P->componentInfoY.offset = sizeof(PlanarPixmapInfoYUV420);
|
|
527 P->componentInfoCb.offset = P->componentInfoY.offset + image_size / 2;
|
|
528 P->componentInfoCr.offset = P->componentInfoCb.offset + image_size / 4;
|
12487
|
529 P->componentInfoY.rowBytes = imgRect.right;
|
|
530 P->componentInfoCb.rowBytes = imgRect.right / 2;
|
|
531 P->componentInfoCr.rowBytes = imgRect.right / 2;
|
12432
|
532 image_buffer_size = image_size + sizeof(PlanarPixmapInfoYUV420);
|
|
533 break;
|
|
534 case IMGFMT_UYVY:
|
|
535 case IMGFMT_YUY2:
|
|
536 image_buffer_size = image_size;
|
|
537 break;
|
|
538 }
|
12414
|
539
|
12432
|
540 qterr = DecompressSequenceBeginS(&seqId,
|
12414
|
541 yuv_qt_stuff.desc,
|
12432
|
542 (char *)P,
|
12414
|
543 image_buffer_size,
|
|
544 GetWindowPort(theWindow),
|
12432
|
545 NULL,
|
|
546 NULL,
|
12414
|
547 ((d_width != width) || (d_height != height)) ?
|
12432
|
548 &matrix : NULL,
|
12414
|
549 srcCopy,
|
12432
|
550 NULL,
|
|
551 0,
|
12414
|
552 codecLosslessQuality,
|
|
553 bestSpeedCodec);
|
12432
|
554
|
|
555 if (qterr)
|
|
556 {
|
|
557 mp_msg(MSGT_VO, MSGL_FATAL, "Quartz error: DecompressSequenceBeginS (%d)\n", qterr);
|
12487
|
558 return -1;
|
12432
|
559 }
|
12424
|
560 }
|
12432
|
561 break;
|
12414
|
562 }
|
|
563
|
12424
|
564 //Show window
|
|
565 RepositionWindow(theWindow, NULL, kWindowCascadeOnMainScreen);
|
12296
|
566 ShowWindow (theWindow);
|
|
567
|
|
568 if(vo_fs)
|
|
569 window_fullscreen();
|
|
570
|
|
571 if(vo_ontop)
|
|
572 window_ontop();
|
|
573
|
|
574 return 0;
|
12120
|
575 }
|
|
576
|
12296
|
577 static void check_events(void)
|
12120
|
578 {
|
12296
|
579 EventRef theEvent;
|
|
580 EventTargetRef theTarget;
|
|
581 OSStatus theErr;
|
|
582
|
|
583 //Get event
|
|
584 theTarget = GetEventDispatcherTarget();
|
|
585 theErr = ReceiveNextEvent(0, 0, kEventDurationNoWait,true, &theEvent);
|
|
586 if(theErr == noErr && theEvent != NULL)
|
|
587 {
|
|
588 SendEventToEventTarget (theEvent, theTarget);
|
|
589 ReleaseEvent(theEvent);
|
|
590 }
|
12120
|
591
|
12296
|
592 //update activity every 30 seconds to prevent
|
|
593 //screensaver from starting up.
|
|
594 DateTimeRec d;
|
|
595 unsigned long curTime;
|
|
596 static unsigned long lastTime = 0;
|
|
597
|
|
598 GetTime(&d);
|
|
599 DateToSeconds( &d, &curTime);
|
|
600
|
|
601 if( ( (curTime - lastTime) >= 30) || (lastTime == 0))
|
|
602 {
|
|
603 UpdateSystemActivity(UsrActivity);
|
|
604 lastTime = curTime;
|
|
605 }
|
|
606 }
|
12120
|
607
|
12296
|
608 static void draw_osd(void)
|
|
609 {
|
12487
|
610 vo_draw_text(imgRect.right,imgRect.bottom,draw_alpha);
|
12296
|
611 }
|
12120
|
612
|
12296
|
613 static void flip_page(void)
|
|
614 {
|
12432
|
615 switch (image_format)
|
|
616 {
|
|
617 case IMGFMT_RGB32:
|
|
618 {
|
12487
|
619 if (EnterMoviesDone && (image_data != NULL))
|
12432
|
620 {
|
|
621 OSErr qterr;
|
|
622 CodecFlags flags = 0;
|
|
623
|
|
624 qterr = DecompressSequenceFrameWhen(seqId,
|
|
625 image_data,
|
|
626 image_size,
|
|
627 0,
|
|
628 &flags,
|
|
629 NULL,
|
|
630 NULL);
|
12487
|
631 image_data = NULL;
|
12432
|
632 if (qterr)
|
|
633 {
|
12487
|
634 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: DecompressSequenceFrameWhen in flip_page (%d) flags:0x%08x\n", qterr, flags);
|
12432
|
635 }
|
|
636 }
|
|
637 }
|
|
638 break;
|
12414
|
639
|
12433
|
640 case IMGFMT_YV12:
|
|
641 case IMGFMT_IYUV:
|
|
642 case IMGFMT_I420:
|
|
643 case IMGFMT_UYVY:
|
|
644 case IMGFMT_YUY2:
|
|
645 if (EnterMoviesDone)
|
|
646 {
|
|
647 OSErr qterr;
|
|
648 CodecFlags flags = 0;
|
|
649 qterr = DecompressSequenceFrameWhen(seqId,
|
|
650 (char *)P,
|
|
651 image_buffer_size,
|
|
652 0, //codecFlagUseImageBuffer,
|
|
653 &flags,
|
|
654 NULL,
|
|
655 NULL);
|
|
656 if (qterr)
|
|
657 {
|
12487
|
658 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: DecompressSequenceFrameWhen in flip_page (%d) flags:0x%08x\n", qterr, flags);
|
12433
|
659 }
|
|
660 }
|
|
661 break;
|
|
662 }
|
12296
|
663 }
|
|
664
|
|
665 static uint32_t draw_slice(uint8_t *src[], int stride[], int w,int h,int x,int y)
|
|
666 {
|
12433
|
667 switch (image_format)
|
|
668 {
|
12487
|
669 case IMGFMT_YV12:
|
|
670 case IMGFMT_I420:
|
|
671 memcpy_pic(((char*)P) + P->componentInfoY.offset + x + imgRect.right * y, src[0], w, h, imgRect.right, stride[0]);
|
|
672 x=x/2;y=y/2;w=w/2;h=h/2;
|
|
673
|
|
674 memcpy_pic(((char*)P) + P->componentInfoCb.offset + x + imgRect.right / 2 * y, src[1], w, h, imgRect.right / 2, stride[1]);
|
|
675 memcpy_pic(((char*)P) + P->componentInfoCr.offset + x + imgRect.right / 2 * y, src[2], w, h, imgRect.right / 2, stride[2]);
|
|
676 return 0;
|
|
677
|
|
678 case IMGFMT_IYUV:
|
|
679 memcpy_pic(((char*)P) + P->componentInfoY.offset + x + imgRect.right * y, src[0], w, h, imgRect.right, stride[0]);
|
|
680 x=x/2;y=y/2;w=w/2;h=h/2;
|
|
681
|
|
682 memcpy_pic(((char*)P) + P->componentInfoCr.offset + x + imgRect.right / 2 * y, src[1], w, h, imgRect.right / 2, stride[1]);
|
|
683 memcpy_pic(((char*)P) + P->componentInfoCb.offset + x + imgRect.right / 2 * y, src[2], w, h, imgRect.right / 2, stride[2]);
|
|
684 return 0;
|
12433
|
685 }
|
12296
|
686 return -1;
|
|
687 }
|
|
688
|
|
689 static uint32_t draw_frame(uint8_t *src[])
|
|
690 {
|
12433
|
691 switch (image_format)
|
|
692 {
|
|
693 case IMGFMT_RGB32:
|
|
694 image_data = src[0];
|
|
695 return 0;
|
12432
|
696
|
12433
|
697 case IMGFMT_UYVY:
|
|
698 case IMGFMT_YUY2:
|
12487
|
699 memcpy_pic(((char*)P), src[0], imgRect.right * 2, imgRect.bottom, imgRect.right * 2, imgRect.right * 2);
|
12433
|
700 return 0;
|
|
701 }
|
|
702 return -1;
|
12296
|
703 }
|
12120
|
704
|
12296
|
705 static uint32_t query_format(uint32_t format)
|
|
706 {
|
|
707 image_format = format;
|
12432
|
708 image_qtcodec = 0;
|
|
709
|
|
710 if (format == IMGFMT_RGB32)
|
|
711 {
|
12487
|
712 return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN;
|
12432
|
713 }
|
12414
|
714
|
12424
|
715 if ((format == IMGFMT_YV12) || (format == IMGFMT_IYUV) || (format == IMGFMT_I420))
|
|
716 {
|
|
717 image_qtcodec = kMpegYUV420CodecType; //kYUV420CodecType ?;
|
12487
|
718 return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE;
|
12414
|
719 }
|
|
720
|
12424
|
721 if (format == IMGFMT_YUY2)
|
|
722 {
|
|
723 image_qtcodec = kComponentVideoUnsigned;
|
12487
|
724 return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN;
|
12414
|
725 }
|
|
726
|
12424
|
727 if (format == IMGFMT_UYVY)
|
|
728 {
|
|
729 image_qtcodec = k422YpCbCr8CodecType;
|
12487
|
730 return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN;
|
12424
|
731 }
|
12414
|
732
|
12296
|
733 return 0;
|
|
734 }
|
12120
|
735
|
12296
|
736 static void uninit(void)
|
|
737 {
|
12432
|
738 OSErr qterr;
|
12424
|
739
|
12432
|
740 if (EnterMoviesDone)
|
|
741 {
|
|
742 qterr = CDSequenceEnd(seqId);
|
|
743 if (qterr)
|
|
744 {
|
12487
|
745 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: CDSequenceEnd (%d)\n", qterr);
|
12424
|
746 }
|
12414
|
747 }
|
12432
|
748
|
12296
|
749 ShowMenuBar();
|
|
750 }
|
12120
|
751
|
12296
|
752 static uint32_t preinit(const char *arg)
|
|
753 {
|
|
754 return 0;
|
12120
|
755 }
|
|
756
|
12424
|
757 static uint32_t draw_yuv_image(mp_image_t *mpi)
|
|
758 {
|
|
759 // ATM we're only called for planar IMGFMT
|
|
760 // drawing is done directly in P
|
|
761 // and displaying is in flip_page.
|
12487
|
762 return get_image_done ? VO_TRUE : VO_FALSE;
|
12414
|
763 }
|
|
764
|
12424
|
765 static uint32_t get_yuv_image(mp_image_t *mpi)
|
|
766 {
|
|
767 if(mpi->type!=MP_IMGTYPE_EXPORT) return VO_FALSE;
|
12414
|
768
|
12424
|
769 if(mpi->imgfmt!=image_format) return VO_FALSE;
|
12414
|
770
|
12424
|
771 if(mpi->flags&MP_IMGFLAG_PLANAR)
|
|
772 {
|
|
773 if (mpi->num_planes != 3)
|
|
774 {
|
|
775 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: only 3 planes allowed in get_yuv_image for planar (%d) \n", mpi->num_planes);
|
|
776 return VO_FALSE;
|
|
777 }
|
12414
|
778
|
12424
|
779 mpi->planes[0]=((char*)P) + P->componentInfoY.offset;
|
12487
|
780 mpi->stride[0]=imgRect.right;
|
|
781 mpi->width=imgRect.right;
|
12414
|
782
|
12424
|
783 if(mpi->flags&MP_IMGFLAG_SWAPPED)
|
|
784 {
|
|
785 // I420
|
|
786 mpi->planes[1]=((char*)P) + P->componentInfoCb.offset;
|
|
787 mpi->planes[2]=((char*)P) + P->componentInfoCr.offset;
|
12487
|
788 mpi->stride[1]=imgRect.right/2;
|
|
789 mpi->stride[2]=imgRect.right/2;
|
12424
|
790 }
|
|
791 else
|
|
792 {
|
|
793 // YV12
|
|
794 mpi->planes[1]=((char*)P) + P->componentInfoCr.offset;
|
|
795 mpi->planes[2]=((char*)P) + P->componentInfoCb.offset;
|
12487
|
796 mpi->stride[1]=imgRect.right/2;
|
|
797 mpi->stride[2]=imgRect.right/2;
|
12424
|
798 }
|
|
799
|
|
800 mpi->flags|=MP_IMGFLAG_DIRECT;
|
12487
|
801 get_image_done = 1;
|
12424
|
802 return VO_TRUE;
|
|
803 }
|
|
804 else
|
12433
|
805 {
|
|
806 // doesn't work yet
|
12424
|
807 if (mpi->num_planes != 1)
|
|
808 {
|
|
809 mp_msg(MSGT_VO, MSGL_ERR, "Quartz error: only 1 plane allowed in get_yuv_image for packed (%d) \n", mpi->num_planes);
|
|
810 return VO_FALSE;
|
|
811 }
|
12414
|
812
|
12424
|
813 mpi->planes[0] = (char*)P;
|
12487
|
814 mpi->stride[0] = imgRect.right * 2;
|
|
815 mpi->width=imgRect.right;
|
12424
|
816 mpi->flags|=MP_IMGFLAG_DIRECT;
|
12487
|
817 get_image_done = 1;
|
12424
|
818 return VO_TRUE;
|
|
819 }
|
|
820 return VO_FALSE;
|
12414
|
821 }
|
|
822
|
12296
|
823 static uint32_t control(uint32_t request, void *data, ...)
|
12120
|
824 {
|
12424
|
825 switch (request)
|
|
826 {
|
|
827 case VOCTRL_PAUSE: return (int_pause=1);
|
|
828 case VOCTRL_RESUME: return (int_pause=0);
|
12460
|
829 case VOCTRL_FULLSCREEN: vo_fs = (!(vo_fs)); window_fullscreen(); return VO_TRUE;
|
|
830 case VOCTRL_ONTOP: vo_ontop = (!(vo_ontop)); window_ontop(); return VO_TRUE;
|
12424
|
831 case VOCTRL_QUERY_FORMAT: return query_format(*((uint32_t*)data));
|
|
832 case VOCTRL_GET_IMAGE:
|
|
833 switch (image_format)
|
|
834 {
|
|
835 case IMGFMT_YV12:
|
|
836 case IMGFMT_IYUV:
|
|
837 case IMGFMT_I420:
|
12487
|
838 case IMGFMT_UYVY:
|
|
839 case IMGFMT_YUY2:
|
12424
|
840 return get_yuv_image(data);
|
|
841 break;
|
12487
|
842 default:
|
|
843 break;
|
12424
|
844 }
|
|
845 case VOCTRL_DRAW_IMAGE:
|
|
846 switch (image_format)
|
|
847 {
|
|
848 case IMGFMT_YV12:
|
|
849 case IMGFMT_IYUV:
|
|
850 case IMGFMT_I420:
|
12487
|
851 case IMGFMT_UYVY:
|
|
852 case IMGFMT_YUY2:
|
12424
|
853 return draw_yuv_image(data);
|
|
854 break;
|
12487
|
855 default:
|
|
856 break;
|
12424
|
857 }
|
|
858 }
|
|
859 return VO_NOTIMPL;
|
12120
|
860 }
|
|
861
|
12296
|
862 void window_resized()
|
12120
|
863 {
|
12296
|
864 float aspectX;
|
|
865 float aspectY;
|
|
866
|
|
867 int padding;
|
|
868
|
|
869 uint32_t d_width;
|
|
870 uint32_t d_height;
|
|
871
|
|
872 GetWindowPortBounds(theWindow, &winRect);
|
12120
|
873
|
12296
|
874 aspect( &d_width, &d_height, A_NOZOOM);
|
|
875
|
|
876 aspectX = (float)((float)winRect.right/(float)d_width);
|
|
877 aspectY = (float)((float)winRect.bottom/(float)d_height);
|
|
878
|
|
879 if((d_height*aspectX)>winRect.bottom)
|
|
880 {
|
|
881 padding = (winRect.right - d_width*aspectY)/2;
|
|
882 SetRect(&dstRect, padding, 0, d_width*aspectY+padding, d_height*aspectY);
|
|
883 }
|
|
884 else
|
|
885 {
|
|
886 padding = (winRect.bottom - d_height*aspectX)/2;
|
|
887 SetRect(&dstRect, 0, padding, (d_width*aspectX), d_height*aspectX+padding);
|
|
888 }
|
12120
|
889
|
12296
|
890 //create a graphic context for the window
|
|
891 SetPortBounds(GetWindowPort(theWindow), &winRect);
|
|
892 CreateCGContextForPort(GetWindowPort(theWindow),&context);
|
|
893
|
|
894 //fill background with black
|
12425
|
895 CGRect winBounds = CGRectMake( winRect.top, winRect.left, winRect.right, winRect.bottom);
|
|
896 CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
|
|
897 CGContextFillRect(context, winBounds);
|
|
898 CGContextFlush(context);
|
12414
|
899
|
12487
|
900 long scale_X = FixDiv(Long2Fix(dstRect.right - dstRect.left),Long2Fix(imgRect.right));
|
|
901 long scale_Y = FixDiv(Long2Fix(dstRect.bottom - dstRect.top),Long2Fix(imgRect.bottom));
|
12414
|
902
|
12432
|
903 SetIdentityMatrix(&matrix);
|
12487
|
904 if (((dstRect.right - dstRect.left) != imgRect.right) || ((dstRect.bottom - dstRect.right) != imgRect.bottom))
|
12432
|
905 {
|
|
906 ScaleMatrix(&matrix, scale_X, scale_Y, 0, 0);
|
12414
|
907
|
12432
|
908 if (padding > 0)
|
|
909 {
|
|
910 TranslateMatrix(&matrix, Long2Fix(dstRect.left), Long2Fix(dstRect.top));
|
|
911 }
|
|
912 }
|
12424
|
913
|
12432
|
914 SetDSequenceMatrix(seqId, &matrix);
|
12120
|
915 }
|
|
916
|
12296
|
917 void window_ontop()
|
12460
|
918 {
|
12432
|
919 if(vo_ontop)
|
12296
|
920 SetWindowClass( theWindow, kUtilityWindowClass);
|
|
921 else
|
|
922 SetWindowClass( theWindow, kDocumentWindowClass);
|
12120
|
923 }
|
|
924
|
12296
|
925 void window_fullscreen()
|
12120
|
926 {
|
12296
|
927 GDHandle deviceHdl;
|
|
928 Rect deviceRect;
|
12120
|
929
|
12296
|
930 //go fullscreen
|
12432
|
931 if(vo_fs)
|
12296
|
932 {
|
|
933 HideMenuBar();
|
12120
|
934
|
12296
|
935 //save old window size
|
12487
|
936 if (!vo_quartz_fs)
|
|
937 GetWindowPortBounds(theWindow, &oldWinRect);
|
12296
|
938
|
|
939 //hide mouse cursor
|
|
940 HideCursor();
|
|
941
|
|
942 //go fullscreen
|
12432
|
943 //ChangeWindowAttributes(theWindow, 0, kWindowResizableAttribute);
|
12425
|
944
|
12296
|
945 MoveWindow (theWindow, 0, 0, 1);
|
|
946 SizeWindow(theWindow, device_width, device_height,1);
|
12487
|
947
|
|
948 vo_quartz_fs = 1;
|
12296
|
949 }
|
|
950 else //go back to windowed mode
|
|
951 {
|
|
952 ShowMenuBar();
|
12120
|
953
|
12296
|
954 //show mouse cursor
|
|
955 ShowCursor();
|
|
956
|
|
957 //revert window to previous setting
|
12432
|
958 //ChangeWindowAttributes(theWindow, kWindowResizableAttribute, 0);
|
12425
|
959
|
12487
|
960 SizeWindow(theWindow, oldWinRect.right, oldWinRect.bottom,1);
|
12296
|
961 RepositionWindow(theWindow, NULL, kWindowCascadeOnMainScreen);
|
12487
|
962
|
|
963 vo_quartz_fs = 0;
|
12296
|
964 }
|
|
965
|
|
966 window_resized();
|
12120
|
967 }
|