in plugin/:
[moon.git] / src / runtime.cpp
blob8da20fed4314a6b4f5ce00eca4e7ed8155c3ad0e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * runtime.cpp: Core surface and canvas definitions.
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
14 #include <config.h>
16 #include <gtk/gtk.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <malloc.h>
21 #include <math.h>
23 #define Visual _XxVisual
24 #define Region _XxRegion
25 #include <gdk/gdkx.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <cairo-xlib.h>
28 #undef Visual
29 #undef Region
31 #include "runtime.h"
32 #include "canvas.h"
33 #include "color.h"
34 #include "shape.h"
35 #include "transform.h"
36 #include "animation.h"
37 #include "downloader.h"
38 #include "frameworkelement.h"
39 #include "textblock.h"
40 #include "media.h"
41 #include "stylus.h"
42 #include "rect.h"
43 #include "panel.h"
44 #include "value.h"
45 #include "namescope.h"
46 #include "xaml.h"
47 #include "dirty.h"
48 #include "fullscreen.h"
49 #include "utils.h"
50 #include "window-gtk.h"
51 #include "timemanager.h"
53 #include "contentcontrol.h"
54 #include "usercontrol.h"
55 #include "deployment.h"
56 #include "grid.h"
57 #include "cbinding.h"
58 #include "tabnavigationwalker.h"
60 //#define DEBUG_INVALIDATE 1
61 //#define RENDER_INDIVIDUALLY 1
62 #define DEBUG_REFCNT 0
64 #define CAIRO_CLIP 0
65 #define TIME_CLIP 0
66 #define TIME_REDRAW 1
68 #define NO_EVENT_ID -1
70 bool Surface::main_thread_inited = false;
71 pthread_t Surface::main_thread = 0;
73 static bool inited = false;
74 static bool g_type_inited = false;
75 static GList* surface_list = NULL;
76 guint32 moonlight_flags = 0;
77 #if DEBUG
78 guint32 debug_flags_ex = 0;
79 guint32 debug_flags = 0;
80 #endif
83 struct env_options {
84 const char *name;
85 guint64 flag;
86 bool set;
89 static struct env_options overrides[] = {
90 // There's no "ms-codecs=yes" option to not allow enabling them from the command line.
91 { "ms-codecs=no", RUNTIME_INIT_ENABLE_MS_CODECS, false },
92 { "ffmpeg-codecs=no", RUNTIME_INIT_DISABLE_FFMPEG_CODECS, true },
93 { "ffmpeg-codecs=yes", RUNTIME_INIT_DISABLE_FFMPEG_CODECS, false },
94 { "timesource=manual", RUNTIME_INIT_MANUAL_TIMESOURCE, true },
95 { "timesource=system", RUNTIME_INIT_MANUAL_TIMESOURCE, false },
96 { "expose=show", RUNTIME_INIT_SHOW_EXPOSE, true },
97 { "expose=hide", RUNTIME_INIT_SHOW_EXPOSE, false },
98 { "clipping=show", RUNTIME_INIT_SHOW_CLIPPING, true },
99 { "clipping=hide", RUNTIME_INIT_SHOW_CLIPPING, false },
100 { "bbox=show", RUNTIME_INIT_SHOW_BOUNDING_BOXES, true },
101 { "bbox=hide", RUNTIME_INIT_SHOW_BOUNDING_BOXES, false },
102 { "textbox=show", RUNTIME_INIT_SHOW_TEXTBOXES, true },
103 { "textbox=hide", RUNTIME_INIT_SHOW_TEXTBOXES, false },
104 { "fps=show", RUNTIME_INIT_SHOW_FPS, true },
105 { "fps=hide", RUNTIME_INIT_SHOW_FPS, false },
106 { "render=ftb", RUNTIME_INIT_RENDER_FRONT_TO_BACK, true },
107 { "render=btf", RUNTIME_INIT_RENDER_FRONT_TO_BACK, false },
108 { "cache=show", RUNTIME_INIT_SHOW_CACHE_SIZE, true },
109 { "cache=hide", RUNTIME_INIT_SHOW_CACHE_SIZE, false },
110 { "converter=default", RUNTIME_INIT_FFMPEG_YUV_CONVERTER, false },
111 { "converter=ffmpeg", RUNTIME_INIT_FFMPEG_YUV_CONVERTER, true },
112 { "shapecache=yes", RUNTIME_INIT_USE_SHAPE_CACHE, true },
113 { "shapecache=no", RUNTIME_INIT_USE_SHAPE_CACHE, false },
114 { "updatepos=yes", RUNTIME_INIT_USE_UPDATE_POSITION, true },
115 { "updatepos=no", RUNTIME_INIT_USE_UPDATE_POSITION, false },
116 { "windowless=yes", RUNTIME_INIT_ALLOW_WINDOWLESS, true },
117 { "windowless=no", RUNTIME_INIT_ALLOW_WINDOWLESS, false },
118 { "audio=alsa", RUNTIME_INIT_AUDIO_ALSA, true },
119 { "audio=alsa-mmap", RUNTIME_INIT_AUDIO_ALSA_MMAP, true },
120 { "audio=alsa-rw", RUNTIME_INIT_AUDIO_ALSA_RW, true },
121 { "audio=pulseaudio", RUNTIME_INIT_AUDIO_PULSE, true },
122 { "idlehint=yes", RUNTIME_INIT_USE_IDLE_HINT, false },
123 { "idlehint=no", RUNTIME_INIT_USE_IDLE_HINT, true },
124 { "backend=xlib", RUNTIME_INIT_USE_BACKEND_XLIB, true },
125 { "backend=image", RUNTIME_INIT_USE_BACKEND_XLIB, false },
126 { "keepmedia=no", RUNTIME_INIT_KEEP_MEDIA, false },
127 { "keepmedia=yes", RUNTIME_INIT_KEEP_MEDIA, true },
128 { "allimages=no", RUNTIME_INIT_ALL_IMAGE_FORMATS, false },
129 { "allimages=yes", RUNTIME_INIT_ALL_IMAGE_FORMATS, true },
132 #define RUNTIME_INIT_DESKTOP (RUNTIME_INIT_PANGO_TEXT_LAYOUT | RUNTIME_INIT_RENDER_FRONT_TO_BACK | RUNTIME_INIT_USE_UPDATE_POSITION | RUNTIME_INIT_USE_SHAPE_CACHE | RUNTIME_INIT_USE_IDLE_HINT | RUNTIME_INIT_USE_BACKEND_XLIB | RUNTIME_INIT_ALL_IMAGE_FORMATS | RUNTIME_INIT_DESKTOP_EXTENSIONS)
133 #define RUNTIME_INIT_BROWSER (RUNTIME_INIT_RENDER_FRONT_TO_BACK | RUNTIME_INIT_USE_UPDATE_POSITION | RUNTIME_INIT_USE_SHAPE_CACHE | RUNTIME_INIT_ALLOW_WINDOWLESS | RUNTIME_INIT_USE_IDLE_HINT | RUNTIME_INIT_USE_BACKEND_XLIB | RUNTIME_INIT_ENABLE_MS_CODECS | RUNTIME_INIT_CREATE_ROOT_DOMAIN)
135 #if DEBUG
136 static struct env_options debugs[] = {
137 { "alsa", RUNTIME_DEBUG_ALSA, true },
138 { "audio", RUNTIME_DEBUG_AUDIO, true },
139 { "pulse", RUNTIME_DEBUG_PULSE, true },
140 { "httpstreaming", RUNTIME_DEBUG_HTTPSTREAMING, true },
141 { "markers", RUNTIME_DEBUG_MARKERS, true },
142 { "mms", RUNTIME_DEBUG_MMS, true },
143 { "mediaplayer", RUNTIME_DEBUG_MEDIAPLAYER, true },
144 { "pipeline", RUNTIME_DEBUG_PIPELINE, true },
145 { "pipeline-error", RUNTIME_DEBUG_PIPELINE_ERROR, true },
146 { "framereaderloop", RUNTIME_DEBUG_FRAMEREADERLOOP, true },
147 { "ui", RUNTIME_DEBUG_UI, true },
148 { "ffmpeg", RUNTIME_DEBUG_FFMPEG, true },
149 { "codecs", RUNTIME_DEBUG_CODECS, true },
150 { "dependencyobject", RUNTIME_DEBUG_DP, true },
151 { "downloader", RUNTIME_DEBUG_DOWNLOADER, true },
152 { "font", RUNTIME_DEBUG_FONT, true },
153 { "layout", RUNTIME_DEBUG_LAYOUT, true },
154 { "media", RUNTIME_DEBUG_MEDIA, true },
155 { "mediaelement", RUNTIME_DEBUG_MEDIAELEMENT, true },
156 { "msi", RUNTIME_DEBUG_MSI, true },
157 { "buffering", RUNTIME_DEBUG_BUFFERING, true },
158 { "asf", RUNTIME_DEBUG_ASF, true },
159 { "playlist", RUNTIME_DEBUG_PLAYLIST, true },
160 { "text", RUNTIME_DEBUG_TEXT, true },
161 { "xaml", RUNTIME_DEBUG_XAML, true },
162 { "deployment", RUNTIME_DEBUG_DEPLOYMENT, true },
163 { "mp3", RUNTIME_DEBUG_MP3, true },
164 { "asf", RUNTIME_DEBUG_ASF, true },
165 { "value", RUNTIME_DEBUG_VALUE, true },
166 { NULL, 0, false }
169 static struct env_options debug_extras[] = {
170 { "alsa-ex", RUNTIME_DEBUG_ALSA_EX, true },
171 { "audio-ex", RUNTIME_DEBUG_AUDIO_EX, true },
172 { "pulse-ex", RUNTIME_DEBUG_PULSE_EX, true },
173 { "markers-ex", RUNTIME_DEBUG_MARKERS_EX, true },
174 { "mediaplayer-ex", RUNTIME_DEBUG_MEDIAPLAYER_EX, true },
175 { "mediaelement-ex", RUNTIME_DEBUG_MEDIAELEMENT_EX, true },
176 { "playlist-ex", RUNTIME_DEBUG_PLAYLIST_EX, true },
177 { "pipeline-ex", RUNTIME_DEBUG_PIPELINE_EX, true },
178 { NULL, 0, false }
180 #endif
184 #define RENDER_EXPOSE (moonlight_flags & RUNTIME_INIT_SHOW_EXPOSE)
186 static bool
187 running_on_nvidia ()
189 int event, error, opcode;
191 Display *display = XOpenDisplay (NULL);
192 bool result = XQueryExtension (display, "NV-GLX", &opcode, &event, &error);
193 XCloseDisplay (display);
195 return result;
198 static void
199 fps_report_default (Surface *surface, int nframes, float nsecs, void *user_data)
201 printf ("Rendered %d frames in %.3fs = %.3f FPS\n", nframes, nsecs, nframes / nsecs);
204 static void
205 cache_report_default (Surface *surface, long bytes, void *user_data)
207 printf ("Cache size is ~%.3f MB\n", bytes / 1048576.0);
210 GList *
211 runtime_get_surface_list (void)
213 if (!Surface::InMainThread ()) {
214 g_warning ("This method can be only called from the main thread!\n");
215 return NULL;
218 return surface_list;
221 static cairo_t *
222 runtime_cairo_create (GdkWindow *drawable, GdkVisual *visual, bool native)
224 int width, height;
225 cairo_surface_t *surface;
226 cairo_t *cr;
228 gdk_drawable_get_size (drawable, &width, &height);
230 if (native)
231 surface = cairo_xlib_surface_create (gdk_x11_drawable_get_xdisplay (drawable),
232 gdk_x11_drawable_get_xid (drawable),
233 GDK_VISUAL_XVISUAL (visual),
234 width, height);
235 else
236 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
238 cr = cairo_create (surface);
239 cairo_surface_destroy (surface);
241 return cr;
244 static gboolean
245 flags_can_be_modifed (void)
247 if (g_list_length (surface_list) != 0) {
248 g_warning ("Flags can be dynamically modified only when there are no surfaces created!");
249 return FALSE;
250 } else if (inited == FALSE) {
251 g_warning ("Runtime has not been initialized yet, your flags will be overriden!");
252 return FALSE;
253 } else
254 return TRUE;
257 void
258 runtime_flags_set_manual_timesource (gboolean flag)
260 if (flags_can_be_modifed ())
261 moonlight_flags |= RUNTIME_INIT_MANUAL_TIMESOURCE;
264 void
265 runtime_flags_set_use_shapecache (gboolean flag)
267 if (flags_can_be_modifed ())
268 moonlight_flags |= RUNTIME_INIT_USE_SHAPE_CACHE;
271 void
272 runtime_flags_set_show_fps (gboolean flag)
274 if (flags_can_be_modifed ())
275 moonlight_flags |= RUNTIME_INIT_SHOW_FPS;
278 /* FIXME More flag setters here */
280 Surface::Surface (MoonWindow *window)
282 SetObjectType (Type::SURFACE);
284 GetDeployment ()->SetSurface (this);
286 main_thread = pthread_self ();
287 main_thread_inited = true;
289 zombie = false;
290 needs_measure = false;
291 needs_arrange = false;
292 downloader_context = NULL;
293 downloaders = NULL;
294 background_color = NULL;
295 cursor = MouseCursorDefault;
296 mouse_event = NULL;
298 background_color = new Color (1, 1, 1, 0);
300 time_manager = new TimeManager ();
301 time_manager->Start ();
302 ticked_after_attach = false;
304 fullscreen_window = NULL;
305 normal_window = active_window = window;
306 if (active_window->IsFullScreen())
307 g_warning ("Surfaces cannot be initialized with fullscreen windows.");
308 window->SetSurface (this);
310 layers = new HitTestCollection ();
311 toplevel = NULL;
312 input_list = new List ();
313 captured = NULL;
315 focused_element = NULL;
316 focus_changed_events = new Queue ();
318 full_screen = false;
319 first_user_initiated_event = false;
320 user_initiated_event = false;
322 full_screen_message = NULL;
323 source_location = NULL;
325 fps_report = fps_report_default;
326 fps_data = NULL;
328 fps_nframes = 0;
329 fps_start = 0;
331 cache_report = cache_report_default;
332 cache_data = NULL;
334 cache_size_in_bytes = 0;
335 cache_size_ticker = 0;
336 cache_size_multiplier = -1;
338 expose_handoff = NULL;
339 expose_handoff_data = NULL;
340 expose_handoff_last_timespan = G_MAXINT64;
342 emittingMouseEvent = false;
343 pendingCapture = NULL;
344 pendingReleaseCapture = false;
346 #ifdef DEBUG
347 debug_selected_element = NULL;
348 #endif
350 up_dirty = new DirtyLists (true);
351 down_dirty = new DirtyLists (false);
353 surface_list = g_list_append (surface_list, this);
356 Surface::~Surface ()
358 time_manager->RemoveHandler (TimeManager::RenderEvent, render_cb, this);
359 time_manager->RemoveHandler (TimeManager::UpdateInputEvent, update_input_cb, this);
361 if (toplevel) {
362 toplevel->SetSurface (NULL);
363 toplevel->unref ();
366 #if DEBUG
367 if (debug_selected_element) {
368 debug_selected_element->unref ();
369 debug_selected_element = NULL;
371 #endif
373 HideFullScreenMessage ();
375 delete input_list;
377 g_free (source_location);
379 if (fullscreen_window)
380 delete fullscreen_window;
382 if (normal_window)
383 delete normal_window;
385 delete background_color;
387 time_manager->unref ();
389 delete up_dirty;
390 delete down_dirty;
392 delete downloaders;
393 layers->unref ();
395 surface_list = g_list_remove (surface_list, this);
398 void
399 Surface::Dispose ()
401 if (toplevel) {
402 toplevel->SetSurface (NULL);
403 toplevel->Dispose ();
406 EventObject::Dispose ();
409 void
410 Surface::Zombify ()
412 time_manager->Shutdown ();
413 DetachDownloaders ();
414 zombie = true;
417 void
418 Surface::SetCursor (MouseCursor new_cursor)
420 if (new_cursor != cursor) {
421 cursor = new_cursor;
423 active_window->SetCursor (cursor);
427 void
428 Surface::AutoFocus ()
430 GenerateFocusChangeEvents ();
433 void
434 Surface::AutoFocusAsync (EventObject *sender)
436 ((Surface *)sender)->AutoFocus ();
439 void
440 Surface::Attach (UIElement *element)
442 bool first = false;
444 #if DEBUG
445 // Attach must be called with NULL to clear out the old canvas
446 // before attaching another canvas, otherwise the new canvas
447 // might get loaded with data from the old canvas (when parsing
448 // xaml ticks will get added to the timemanager of the surface,
449 // if the old canvas isn't gone when the new canvas is parsed,
450 // the ticks will be added to the old timemanager).
451 if (toplevel != NULL && element != NULL)
452 g_warning ("Surface::Attach (NULL) should be called to clear out the old canvas before adding a new canvas.");
453 #endif
455 #if SANITY
456 if (element != NULL && element->GetDeployment () != GetDeployment ())
457 g_warning ("Surface::Attach (%p): trying to attach an object created on the deployment %p on a surface whose deployment is %p\n", element, element->GetDeployment (), GetDeployment ());
458 if (GetDeployment () != Deployment::GetCurrent ())
459 g_warning ("Surface::Attach (%p): current deployment is %p, surface deployment is %p\n", element, GetDeployment (), Deployment::GetCurrent ());
460 #endif
463 if (toplevel) {
464 toplevel->RemoveHandler (UIElement::LoadedEvent, toplevel_loaded, this);
465 DetachLayer (toplevel);
466 time_manager->RemoveHandler (TimeManager::RenderEvent, render_cb, this);
467 time_manager->RemoveHandler (TimeManager::UpdateInputEvent, update_input_cb, this);
468 time_manager->Stop ();
469 int maxframerate = time_manager->GetMaximumRefreshRate ();
470 toplevel->unref ();
471 time_manager->unref ();
472 time_manager = new TimeManager ();
473 time_manager->AddHandler (TimeManager::RenderEvent, render_cb, this);
474 time_manager->AddHandler (TimeManager::UpdateInputEvent, update_input_cb, this);
475 time_manager->SetMaximumRefreshRate (maxframerate);
476 time_manager->NeedRedraw ();
477 time_manager->Start ();
478 } else
479 first = true;
481 if (!element) {
482 DetachDownloaders ();
484 if (first)
485 active_window->EnableEvents (first);
487 active_window->Invalidate();
489 toplevel = NULL;
490 return;
493 if (!element->Is (Type::UIELEMENT)) {
494 printf ("Surface::Attach Unsupported toplevel %s\n", Type::Find (element->GetObjectType ())->GetName ());
495 return;
498 UIElement *canvas = element;
499 canvas->ref ();
501 // make sure we have a namescope at the toplevel so that names
502 // can be registered/resolved properly.
503 if (NameScope::GetNameScope (canvas) == NULL) {
504 NameScope::SetNameScope (canvas, new NameScope());
507 // First time we connect the surface, start responding to events
508 if (first)
509 active_window->EnableEvents (first);
511 if (zombie)
512 return;
514 toplevel = canvas;
515 AttachLayer (canvas);
517 this->ref ();
518 canvas->AddHandler (UIElement::LoadedEvent, toplevel_loaded, this, (GDestroyNotify)event_object_unref);
520 ticked_after_attach = false;
521 time_manager->RemoveTickCall (tick_after_attach_reached, this);
522 time_manager->AddTickCall (tick_after_attach_reached, this);
525 void
526 Surface::tick_after_attach_reached (EventObject *data)
528 Surface *surface = (Surface*)data;
530 surface->ticked_after_attach = true;
531 surface->Emit (Surface::LoadEvent);
534 void
535 Surface::toplevel_loaded (EventObject *sender, EventArgs *args, gpointer closure)
537 ((Surface*)closure)->ToplevelLoaded ((UIElement*)sender);
540 void
541 Surface::ToplevelLoaded (UIElement *element)
543 if (element == toplevel) {
544 toplevel->RemoveHandler (UIElement::LoadedEvent, toplevel_loaded, this);
546 // FIXME: If the element is supposed to be focused, FocusElement (element)
547 // should be used. I think this is unnecessary anyway.
548 //if (active_window && active_window->HasFocus())
549 // element->EmitGotFocus ();
552 // If the did not get a size specified
554 if (normal_window && normal_window->GetWidth() == 0 && normal_window->GetHeight() == 0 && toplevel) {
556 * this should only be hit in the nonplugin case ans is
557 * simply here to give a reasonable default size
559 Value *vh, *vw;
560 vw = toplevel->GetValue (FrameworkElement::WidthProperty);
561 vh = toplevel->GetValue (FrameworkElement::HeightProperty);
562 if (vh || vw)
563 normal_window->Resize (MAX (vw ? (int)vw->AsDouble () : 0, 0),
564 MAX (vh ? (int)vh->AsDouble () : 0, 0));
567 Emit (ResizeEvent);
569 element->UpdateTotalRenderVisibility ();
570 element->UpdateTotalHitTestVisibility ();
571 element->FullInvalidate (true);
573 // we call this two here so that the layout pass proceeds when
574 // we next process the dirty list.
575 element->InvalidateMeasure ();
579 void
580 Surface::AttachLayer (UIElement *layer)
582 if (layer == toplevel)
583 layers->Insert (0, Value(layer));
584 else
585 layers->Add (Value (layer));
587 layer->SetSurface (this);
588 layer->FullInvalidate (true);
590 List *list = layer->WalkTreeForLoaded (NULL);
591 layer->PostSubtreeLoad (list);
592 // PostSubtreeLoad will take care of deleting the list for us.
595 void
596 Surface::DetachLayer (UIElement *layer)
598 layers->Remove (Value (layer));
599 layer->SetSurface (NULL);
600 if (active_window)
601 Invalidate (layer->GetBounds ());
604 void
605 Surface::Invalidate (Rect r)
607 active_window->Invalidate (r);
610 void
611 Surface::ProcessUpdates ()
613 active_window->ProcessUpdates();
616 void
617 Surface::Paint (cairo_t *ctx, int x, int y, int width, int height)
619 Rect r = Rect (x, y, width, height);
620 Region region = Region (r);
621 Paint (ctx, &region);
624 void
625 Surface::Paint (cairo_t *ctx, Region *region)
627 for (int i = 0; i < layers->GetCount (); i++) {
628 UIElement *layer = layers->GetValueAt (i)->AsUIElement ();
629 layer->Paint (ctx, region, NULL);
632 #ifdef DEBUG
633 if (debug_selected_element) {
634 Rect bounds = debug_selected_element->GetSubtreeBounds();
635 // printf ("debug_selected_element is %s\n", debug_selected_element->GetName());
636 // printf ("bounds is %g %g %g %g\n", bounds.x, bounds.y, bounds.w, bounds.h);
637 cairo_save (ctx);
638 //RenderClipPath (ctx);
639 cairo_new_path (ctx);
640 cairo_identity_matrix (ctx);
641 cairo_set_source_rgba (ctx, 1.0, 0.5, 0.2, 1.0);
642 cairo_set_line_width (ctx, 1);
643 cairo_rectangle (ctx, bounds.x, bounds.y, bounds.width, bounds.height);
644 cairo_stroke (ctx);
645 cairo_restore (ctx);
647 #endif
651 // This will resize the surface (merely a convenience function for
652 // resizing the widget area that we have.
654 // This will not change the Width and Height properties of the
655 // toplevel canvas, if you want that, you must do that yourself
657 void
658 Surface::Resize (int width, int height)
660 if (width == normal_window->GetWidth()
661 && height == normal_window->GetHeight())
662 return;
664 normal_window->Resize (width, height);
667 void
668 Surface::EmitSourceDownloadComplete ()
670 Emit (SourceDownloadCompleteEvent, NULL);
673 void
674 Surface::EmitSourceDownloadProgressChanged (DownloadProgressEventArgs *args)
676 Emit (SourceDownloadProgressChangedEvent, args);
679 void
680 Surface::EmitError (ErrorEventArgs *args)
682 Emit (ErrorEvent, args);
685 void
686 Surface::EmitError (int number, int code, const char *message)
688 EmitError (new ErrorEventArgs ((ErrorEventArgsType)number,
689 MoonError (MoonError::EXCEPTION, code, message)));
692 void
693 Surface::Realloc ()
695 for (int i = 0; i < layers->GetCount (); i++) {
696 UIElement *layer = layers->GetValueAt (i)->AsUIElement ();
698 layer->InvalidateMeasure ();
699 //layer->UpdateBounds();
703 void
704 Surface::SetFullScreen (bool value)
706 if (value && !IsUserInitiatedEvent ()) {
707 g_warning ("You're not allowed to switch to fullscreen from where you're doing it.");
708 return;
711 UpdateFullScreen (value);
714 void
715 Surface::SetUserInitiatedEvent (bool value)
717 GenerateFocusChangeEvents ();
718 first_user_initiated_event = first_user_initiated_event | value;
719 user_initiated_event = value;
722 bool
723 Surface::IsTopLevel (UIElement* top)
725 if (top == NULL)
726 return false;
728 bool ret = top == full_screen_message;
730 for (int i = 0; i < layers->GetCount () && !ret; i++)
731 ret = layers->GetValueAt (i)->AsUIElement () == top;
733 return ret;
736 void
737 Surface::ShowFullScreenMessage ()
739 g_return_if_fail (full_screen_message == NULL);
740 //g_return_if_fail (toplevel && toplevel->Is (Type::PANEL));
742 Type::Kind dummy;
743 XamlLoader *loader = new XamlLoader (NULL, FULLSCREEN_MESSAGE, this);
744 DependencyObject* message = loader->CreateDependencyObjectFromString (FULLSCREEN_MESSAGE, false, &dummy);
745 delete loader;
747 if (!message) {
748 printf ("Unable to create fullscreen message.\n");
749 return;
752 if (!message->Is (Type::CANVAS)) {
753 printf ("Unable to create fullscreen message, got a %s, expected at least a UIElement.\n", message->GetTypeName ());
754 message->unref ();
755 return;
758 full_screen_message = (Canvas*) message;
759 AttachLayer (full_screen_message);
761 DependencyObject* message_object = full_screen_message->FindName ("message");
762 DependencyObject* url_object = full_screen_message->FindName ("url");
763 TextBlock* message_block = (message_object != NULL && message_object->Is (Type::TEXTBLOCK)) ? (TextBlock*) message_object : NULL;
764 TextBlock* url_block = (url_object != NULL && url_object->Is (Type::TEXTBLOCK)) ? (TextBlock*) url_object : NULL;
766 Transform* transform = full_screen_message->GetRenderTransform ();
768 double box_height = full_screen_message->GetHeight ();
769 double box_width = full_screen_message->GetWidth ();
771 // Set the url in the box
772 if (url_block != NULL) {
773 char *url = NULL;
775 if (source_location) {
776 if (g_str_has_prefix (source_location, "http://")) {
777 const char *path = strchr (source_location + 7, '/');
779 if (path != NULL && path > source_location + 7) {
780 url = g_strndup (source_location, path - source_location);
781 } else {
782 url = g_strdup (source_location);
784 } else if (g_str_has_prefix (source_location, "file://")) {
785 url = g_strdup ("file://");
786 } else {
787 url = g_strdup (source_location);
791 url_block->SetValue (TextBlock::TextProperty, url ? url : (char *) "file://");
792 g_free (url);
795 // The box is not made bigger if the url doesn't fit.
796 // MS has an interesting text rendering if the url doesn't
797 // fit: the text is overflown to the left.
798 // Since only the server is shown, this shouldn't
799 // happen on a regular basis though.
801 // Center the url block
802 if (url_block != NULL) {
803 double url_width = url_block->GetActualWidth ();
804 Canvas::SetLeft (url_block, (box_width - url_width) / 2);
807 // Center the message block
808 if (message_block != NULL) {
809 double message_width = message_block->GetActualWidth ();
810 Canvas::SetLeft (message_block, (box_width - message_width) / 2);
813 // Put the box in the middle of the screen
814 transform->SetValue (TranslateTransform::XProperty, Value ((active_window->GetWidth() - box_width) / 2));
815 transform->SetValue (TranslateTransform::YProperty, Value ((active_window->GetHeight() - box_height) / 2));
818 const char*
819 Surface::GetSourceLocation ()
821 return source_location;
824 void
825 Surface::SetSourceLocation (const char* location)
827 g_free (source_location);
828 source_location = g_strdup (location);
831 void
832 Surface::HideFullScreenMessage ()
834 if (full_screen_message) {
835 DetachLayer (full_screen_message);
836 full_screen_message->unref ();
837 full_screen_message = NULL;
841 void
842 Surface::UpdateFullScreen (bool value)
844 if (value == full_screen)
845 return;
847 if (value) {
848 fullscreen_window = new MoonWindowGtk (true, -1, -1, normal_window);
849 fullscreen_window->SetSurface (this);
851 active_window = fullscreen_window;
853 ShowFullScreenMessage ();
855 fullscreen_window->EnableEvents (false);
856 } else {
857 active_window = normal_window;
859 HideFullScreenMessage ();
861 delete fullscreen_window;
862 fullscreen_window = NULL;
865 full_screen = value;
867 Realloc ();
869 time_manager->GetSource()->Stop();
870 Emit (FullScreenChangeEvent);
872 if (!value)
873 Emit (ResizeEvent);
874 time_manager->GetSource()->Start();
877 void
878 Surface::render_cb (EventObject *sender, EventArgs *calldata, gpointer closure)
880 Surface *s = (Surface *) closure;
881 gint64 now;
882 bool dirty = false;
884 GDK_THREADS_ENTER ();
885 if (s->zombie) {
886 s->up_dirty->Clear (true);
887 s->down_dirty->Clear (true);
888 } else {
889 dirty = s->ProcessDirtyElements ();
892 if (s->expose_handoff) {
893 TimeSpan time = s->GetTimeManager ()->GetCurrentTime ();
894 if (time != s->expose_handoff_last_timespan) {
895 s->expose_handoff (s, time , s->expose_handoff_data);
896 s->expose_handoff_last_timespan = time;
900 GDK_THREADS_LEAVE ();
902 if ((moonlight_flags & RUNTIME_INIT_SHOW_FPS) && s->fps_start == 0)
903 s->fps_start = get_now ();
905 if (dirty) {
906 s->ProcessUpdates ();
909 if ((moonlight_flags & RUNTIME_INIT_SHOW_FPS) && s->fps_report) {
910 s->fps_nframes++;
912 if ((now = get_now ()) > (s->fps_start + TIMESPANTICKS_IN_SECOND)) {
913 float nsecs = (now - s->fps_start) / TIMESPANTICKS_IN_SECOND_FLOAT;
915 s->fps_report (s, s->fps_nframes, nsecs, s->fps_data);
916 s->fps_nframes = 0;
917 s->fps_start = now;
921 if ((moonlight_flags & RUNTIME_INIT_SHOW_CACHE_SIZE) && s->cache_report) {
922 // By default we report cache status every 50 render's.
923 // Should be enough for everybody, but syncing to ie. 1s sounds
924 // better.
925 if (s->cache_size_ticker == 50) {
926 s->cache_report (s, s->cache_size_in_bytes, s->cache_data);
927 s->cache_size_ticker = 0;
928 } else
929 s->cache_size_ticker++;
933 void
934 Surface::update_input_cb (EventObject *sender, EventArgs *calldata, gpointer closure)
936 #if notyet
937 Surface *s = (Surface *) closure;
939 s->HandleMouseEvent (UIElement::MouseMoveEvent, true, true, false, s->mouse_event_state, s->mouse_event_x, s->mouse_event_y);
940 s->UpdateCursorFromInputList ()
941 #endif
944 void
945 Surface::HandleUIWindowAvailable ()
947 time_manager->AddHandler (TimeManager::RenderEvent, render_cb, this);
948 time_manager->AddHandler (TimeManager::UpdateInputEvent, update_input_cb, this);
950 time_manager->NeedRedraw ();
953 void
954 Surface::HandleUIWindowUnavailable ()
956 time_manager->RemoveHandler (TimeManager::RenderEvent, render_cb, this);
957 time_manager->RemoveHandler (TimeManager::UpdateInputEvent, update_input_cb, this);
960 void
961 Surface::PaintToDrawable (GdkDrawable *drawable, GdkVisual *visual, GdkEventExpose *event, int off_x, int off_y, bool transparent, bool clear_transparent)
963 frames++;
965 LOG_UI ("Surface::PaintToDrawable (%p, %p, (%d,%d %d,%d), %d, %d, %d, %d)\n",
966 drawable, visual, event->area.x, event->area.y, event->area.width, event->area.height,
967 off_x, off_y, transparent, clear_transparent);
969 if (event->area.x > (off_x + active_window->GetWidth()) || event->area.y > (off_y + active_window->GetHeight()))
970 return;
972 SetCurrentDeployment ();
974 #if TIME_REDRAW
975 STARTTIMER (expose, "redraw");
976 #endif
977 if (cache_size_multiplier == -1)
978 cache_size_multiplier = gdk_drawable_get_depth (drawable) / 8 + 1;
980 #ifdef DEBUG_INVALIDATE
981 printf ("Got a request to repaint at %d %d %d %d\n", event->area.x, event->area.y, event->area.width, event->area.height);
982 #endif
983 cairo_t *ctx = runtime_cairo_create (drawable, visual, moonlight_flags & RUNTIME_INIT_USE_BACKEND_XLIB);
984 Region *region = new Region (event->region);
986 region->Offset (-off_x, -off_y);
987 cairo_surface_set_device_offset (cairo_get_target (ctx),
988 off_x - event->area.x,
989 off_y - event->area.y);
990 region->Draw (ctx);
992 // These are temporary while we change this to paint at the offset position
993 // instead of using the old approach of modifying the topmost Canvas (a no-no),
995 // The flag "transparent" is here because I could not
996 // figure out what is painting the background with white now.
997 // The change that made the white painting implicit instead of
998 // explicit is patch 80632. I would appreciate any help in tracking down
999 // the proper way of making the background white when not running in
1000 // "transparent" mode.
1002 // Either exposing surface_set_trans to turn the next code is a hack,
1003 // or it is normal to request all code that wants to paint to manually
1004 // clear the background to white beforehand. For now am going with
1005 // making this an explicit surface API.
1007 // The second part is for coping with the future: when we support being
1008 // windowless
1010 cairo_set_operator (ctx, CAIRO_OPERATOR_OVER);
1012 if (transparent) {
1013 if (clear_transparent) {
1014 cairo_set_operator (ctx, CAIRO_OPERATOR_CLEAR);
1015 cairo_fill_preserve (ctx);
1016 cairo_set_operator (ctx, CAIRO_OPERATOR_OVER);
1019 cairo_set_source_rgba (ctx,
1020 background_color->r,
1021 background_color->g,
1022 background_color->b,
1023 background_color->a);
1025 else {
1026 cairo_set_source_rgb (ctx,
1027 background_color->r,
1028 background_color->g,
1029 background_color->b);
1032 cairo_fill_preserve (ctx);
1033 cairo_clip (ctx);
1035 cairo_save (ctx);
1036 Paint (ctx, region);
1037 cairo_restore (ctx);
1039 if (RENDER_EXPOSE) {
1040 cairo_new_path (ctx);
1041 region->Draw (ctx);
1042 cairo_set_line_width (ctx, 2.0);
1043 cairo_set_source_rgb (ctx, (double)(frames % 2), (double)((frames + 1) % 2), (double)((frames / 3) % 2));
1044 cairo_stroke (ctx);
1047 if (!(moonlight_flags & RUNTIME_INIT_USE_BACKEND_XLIB)) {
1048 cairo_surface_flush (cairo_get_target (ctx));
1049 cairo_t *native = runtime_cairo_create (drawable, visual, true);
1051 cairo_surface_set_device_offset (cairo_get_target (native),
1052 0, 0);
1053 cairo_surface_set_device_offset (cairo_get_target (ctx),
1054 0, 0);
1056 cairo_set_source_surface (native, cairo_get_target (ctx),
1057 0, 0);
1059 region->Offset (off_x, off_y);
1060 region->Offset (-event->area.x, -event->area.y);
1061 region->Draw (native);
1063 cairo_fill (native);
1064 cairo_destroy (native);
1066 cairo_destroy (ctx);
1068 delete region;
1071 #if TIME_REDRAW
1072 ENDTIMER (expose, "redraw");
1073 #endif
1077 /* for emitting focus changed events */
1078 class FocusChangedNode : public List::Node {
1079 public:
1080 UIElement *lost_focus;
1081 UIElement *got_focus;
1083 FocusChangedNode (UIElement *lost_focus, UIElement *got_focus);
1084 virtual ~FocusChangedNode ();
1087 FocusChangedNode::FocusChangedNode (UIElement *lost_focus, UIElement *got_focus)
1089 this->lost_focus = lost_focus;
1090 this->got_focus = got_focus;
1092 if (lost_focus)
1093 lost_focus->ref ();
1094 if (got_focus)
1095 got_focus->ref ();
1098 FocusChangedNode::~FocusChangedNode ()
1100 if (lost_focus)
1101 lost_focus->unref ();
1102 if (got_focus)
1103 got_focus->unref ();
1106 RenderNode::RenderNode (UIElement *el,
1107 Region *region,
1108 bool render_element,
1109 RenderFunc pre,
1110 RenderFunc post)
1113 uielement = el;
1114 uielement->ref();
1115 this->region = region ? region : new Region ();
1116 this->render_element = render_element;
1117 this->pre_render = pre;
1118 this->post_render = post;
1121 void
1122 RenderNode::Render (cairo_t *ctx)
1124 bool front_to_back = uielement->UseBackToFront ();
1126 if (pre_render)
1127 pre_render (ctx, uielement, region, front_to_back);
1129 if (render_element)
1130 uielement->Render (ctx, region);
1132 if (post_render)
1133 post_render (ctx, uielement, region, front_to_back);
1136 RenderNode::~RenderNode ()
1138 if (uielement) {
1139 uielement->unref ();
1140 uielement = NULL;
1143 if (region)
1144 delete region;
1147 UIElementNode::UIElementNode (UIElement *el)
1149 uielement = el;
1150 uielement->ref();
1153 UIElementNode::~UIElementNode ()
1155 uielement->unref();
1156 uielement = NULL;
1159 void
1160 Surface::PerformCapture (UIElement *capture)
1162 // "Capturing" the mouse pointer at an element forces us to
1163 // use the path up the hierarchy from that element to the root
1164 // as the input list, regardless of where the pointer actually
1165 // is.
1167 captured = capture;
1168 List *new_input_list = new List();
1169 while (capture) {
1170 new_input_list->Append (new UIElementNode (capture));
1171 capture = capture->GetVisualParent();
1174 delete input_list;
1175 input_list = new_input_list;
1176 pendingCapture = NULL;
1179 void
1180 Surface::PerformReleaseCapture ()
1182 // These need to be set before calling HandleMouseEvent as
1183 // "captured" determines the input_list calculation, and
1184 // "pendingReleaseCapture", when set, causes an infinite
1185 // recursive loop.
1186 UIElement *old_captured = captured;
1187 captured = NULL;
1188 pendingReleaseCapture = false;
1190 old_captured->EmitLostMouseCapture ();
1192 // this causes any new elements we're over to be Enter'ed. MS
1193 // doesn't Leave the element that had the mouse captured,
1194 // though.
1195 HandleMouseEvent (NO_EVENT_ID, false, true, false, mouse_event);
1198 void
1199 Surface::ReleaseMouseCapture (UIElement *capture)
1201 // Mouse capture is only released when the element owning the capture
1202 // requests it
1203 if (capture != captured && capture != pendingCapture)
1204 return;
1206 if (emittingMouseEvent)
1207 pendingReleaseCapture = true;
1208 else
1209 PerformReleaseCapture ();
1212 bool
1213 Surface::SetMouseCapture (UIElement *capture)
1215 if (captured || pendingCapture)
1216 return capture == captured || capture == pendingCapture;
1218 if (!emittingMouseEvent)
1219 return false;
1221 pendingCapture = capture;
1222 return true;
1225 EventArgs*
1226 Surface::CreateArgsForEvent (int event_id, GdkEvent *event)
1228 if (event_id ==UIElement::InvalidatedEvent
1229 || event_id ==UIElement::GotFocusEvent
1230 || event_id ==UIElement::LostFocusEvent)
1231 return new RoutedEventArgs ();
1232 else if (event_id == UIElement::MouseLeaveEvent
1233 || event_id ==UIElement::MouseMoveEvent
1234 || event_id ==UIElement::MouseEnterEvent)
1235 return new MouseEventArgs(event);
1236 else if (event_id ==UIElement::MouseLeftButtonMultiClickEvent
1237 || event_id ==UIElement::MouseLeftButtonDownEvent
1238 || event_id ==UIElement::MouseLeftButtonUpEvent
1239 || event_id ==UIElement::MouseRightButtonDownEvent
1240 || event_id ==UIElement::MouseRightButtonUpEvent)
1241 return new MouseButtonEventArgs(event);
1242 else if (event_id == UIElement::MouseWheelEvent)
1243 return new MouseWheelEventArgs(event);
1244 else if (event_id == UIElement::KeyDownEvent
1245 || event_id == UIElement::KeyUpEvent)
1246 return new KeyEventArgs((GdkEventKey*)event);
1247 else {
1248 g_warning ("Unknown event id %d\n", event_id);
1249 return new EventArgs();
1253 bool
1254 Surface::EmitEventOnList (int event_id, List *element_list, GdkEvent *event, int end_idx)
1256 bool handled = false;
1258 int idx;
1259 UIElementNode *node;
1261 if (element_list->IsEmpty() || end_idx == 0)
1262 return handled;
1264 if (end_idx == -1)
1265 end_idx = element_list->Length();
1267 EmitContext** emit_ctxs = g_new (EmitContext*, end_idx + 1);
1268 for (node = (UIElementNode*)element_list->First(), idx = 0; node && idx < end_idx; node = (UIElementNode*)node->next, idx++) {
1269 emit_ctxs[idx] = node->uielement->StartEmit (event_id);
1272 EventArgs *args = CreateArgsForEvent(event_id, event);
1273 bool args_are_routed = args->Is (Type::ROUTEDEVENTARGS);
1275 if (args_are_routed && element_list->First())
1276 ((RoutedEventArgs*)args)->SetSource(((UIElementNode*)element_list->First())->uielement);
1278 for (node = (UIElementNode*)element_list->First(), idx = 0; node && idx < end_idx; node = (UIElementNode*)node->next, idx++) {
1279 args->ref ();
1280 bool h = node->uielement->DoEmit (event_id, args);
1281 if (h)
1282 handled = true;
1283 if (zombie) {
1284 handled = false;
1285 break;
1288 if (args_are_routed && ((RoutedEventArgs*)args)->GetHandled())
1289 break;
1292 args->unref();
1294 for (node = (UIElementNode*)element_list->First(), idx = 0; node && idx < end_idx; node = (UIElementNode*)node->next, idx++) {
1295 node->uielement->FinishEmit (event_id, emit_ctxs[idx]);
1297 g_free (emit_ctxs);
1299 return handled;
1302 void
1303 Surface::FindFirstCommonElement (List *l1, int *index1,
1304 List *l2, int *index2)
1306 // we exploit the fact that for a list with any common
1307 // elements, the lists will be identical from the first common
1308 // element to the end of the lists. So, we start from the
1309 // last elements in both lists and walk backward to the start,
1310 // looking for the first elements that don't match.
1312 // this algorithm is O(MAX(n,m)), but it's unclear whether or
1313 // not this is actually better than the O(n*m) approach, since
1314 // the O(n*m) approach will often find a match on the first
1315 // comparison (especially when the user is slowly moving the
1316 // mouse around in the same element), and skip the rest.
1317 int i1, i2;
1318 UIElementNode *ui1, *ui2;
1320 *index1 = -1;
1321 *index2 = -1;
1323 ui1 = (UIElementNode*)l1->Last();
1324 i1 = l1->Length() - 1;
1326 ui2 = (UIElementNode*)l2->Last();
1327 i2 = l2->Length() - 1;
1329 while (ui1 && ui2) {
1331 if (ui1->uielement == ui2->uielement) {
1332 *index1 = i1;
1333 *index2 = i2;
1335 else {
1336 return;
1339 ui1 = (UIElementNode*)ui1->prev;
1340 ui2 = (UIElementNode*)ui2->prev;
1341 i1--;
1342 i2--;
1346 static List*
1347 copy_input_list_from_node (List *input_list, UIElementNode* node)
1349 List *list = new List ();
1351 while (node) {
1352 list->Append (new UIElementNode (node->uielement));
1353 node = (UIElementNode*) node->next;
1356 return list;
1359 bool
1360 Surface::HandleMouseEvent (int event_id, bool emit_leave, bool emit_enter, bool force_emit, GdkEvent *event)
1362 bool handled = false;
1363 bool mouse_down = event_id == UIElement::MouseLeftButtonDownEvent ||
1364 event_id == UIElement::MouseRightButtonDownEvent;
1366 if ((moonlight_flags & RUNTIME_INIT_DESKTOP_EXTENSIONS) == 0 &&
1367 ((event_id == UIElement::MouseRightButtonDownEvent) || (event_id == UIElement::MouseRightButtonDownEvent)))
1368 event_id = NO_EVENT_ID;
1370 // we can end up here if mozilla pops up the JS timeout
1371 // dialog. The problem is that JS might have registered a
1372 // handler for the event we're going to emit, so when we end
1373 // up tripping the timeout while in JS, mozilla pops up the
1374 // dialog, which causes a crossing-notify event to be emitted.
1375 // This causes HandleMouseEvent to be called, and the original
1376 // input_list is deleted. the crossing-notify event is
1377 // handled, then we return to the event that tripped the
1378 // timeout, we crash.
1379 if (emittingMouseEvent)
1380 return false;
1382 emittingMouseEvent = true;
1383 if (zombie)
1384 return false;
1386 if (toplevel == NULL || event == NULL)
1387 return false;
1389 // FIXME this should probably use mouse event args
1390 ProcessDirtyElements();
1392 if (captured) {
1393 // if the mouse is captured, the input_list doesn't ever
1394 // change, and we don't emit enter/leave events. just emit
1395 // the event on the input_list.
1396 if (event_id != NO_EVENT_ID)
1397 handled = EmitEventOnList (event_id, input_list, event, -1);
1399 else {
1400 int surface_index;
1401 int new_index;
1403 // Accumulate a new input_list, which contains the
1404 // most deeply nested hit testable UIElement covering
1405 // the point (x,y), and all visual parents up the
1406 // hierarchy to the root.
1407 List *new_input_list = new List ();
1408 double x, y;
1410 gdk_event_get_coords (event, &x, &y);
1412 Point p (x,y);
1414 cairo_t *ctx = measuring_context_create ();
1415 for (int i = layers->GetCount () - 1; i >= 0 && new_input_list->IsEmpty (); i--)
1416 layers->GetValueAt (i)->AsUIElement ()->HitTest (ctx, p, new_input_list);
1418 if (mouse_down) {
1419 GenerateFocusChangeEvents ();
1420 if (!GetFocusedElement ()) {
1421 for (int i = layers->GetCount () - 1; i >= 0; i--) {
1422 if (layers->GetValueAt (i)->AsUIElement ()->Focus ())
1423 break;
1426 GenerateFocusChangeEvents ();
1430 // for 2 lists:
1431 // l1: [a1, a2, a3, a4, ... ]
1432 // l2: [b1, b2, b3, b4, ... ]
1434 // For identical lists:
1436 // only the primary event is emitted for all
1437 // elements of l2, in order.
1439 // For lists that differ, Enter/Leave events must be
1440 // emitted.
1442 // If the first few nodes in each list differ, and,
1443 // for instance bn == am, we know that [am...] ==
1444 // [bn...]
1446 // when emitting a given event on b1, MS generally
1447 // emits Leave events on [a1, a2, a3, ... am-1], and
1448 // Enter events on [b1, b2, ... bn-1].
1450 // For most event types, that's all that happens if
1451 // the lists differ. For MouseLeftButtonDown (we
1452 // also do it for MouseLeftButtonUp), we also emit
1453 // the primary event on l2 after the enter/leave
1454 // events.
1456 FindFirstCommonElement (input_list, &surface_index,
1457 new_input_list, &new_index);
1459 if (emit_leave)
1460 handled = EmitEventOnList (UIElement::MouseLeaveEvent, input_list, event, surface_index);
1462 if (emit_enter)
1463 handled = EmitEventOnList (UIElement::MouseEnterEvent, new_input_list, event, new_index) || handled;
1465 if (event_id != NO_EVENT_ID && ((surface_index == 0 && new_index == 0) || force_emit))
1466 handled = EmitEventOnList (event_id, new_input_list, event, -1) || handled;
1468 // We need to remove from the new_input_list the events which have just
1469 // became invisible/unhittable as the result of the event.
1470 // (ie. element visibility was changed in the mouse enter).
1472 if (handled) {
1473 UIElementNode *node;
1475 for (node = (UIElementNode*)new_input_list->Last(); node; node = (UIElementNode*)node->prev) {
1476 if (! node->uielement->GetRenderVisible () ||
1477 ! node->uielement->GetHitTestVisible ()) {
1478 // Ooops, looks like something changed.
1479 // We need to copy the list with some elements removed.
1480 List *list = copy_input_list_from_node (new_input_list, (UIElementNode*)node->next);
1481 delete new_input_list;
1482 new_input_list = list;
1483 break;
1488 measuring_context_destroy (ctx);
1490 delete input_list;
1491 input_list = new_input_list;
1494 #define SPEW_INPUT_LIST 0
1496 #if SPEW_INPUT_LIST
1498 printf ("input_list: ");
1499 UIElementNode *node;
1500 for (node = (UIElementNode*)input_list->First(); node; node = (UIElementNode*)node->next) {
1501 if (node != input_list->First())
1502 printf ("->");
1503 printf ("(%s)", node->uielement->GetName());
1505 printf ("\n");
1507 #endif
1509 // Perform any captures/releases that are pending after the
1510 // event is bubbled.
1511 if (pendingCapture)
1512 PerformCapture (pendingCapture);
1513 if (pendingReleaseCapture || (captured && !captured->CanCaptureMouse ()))
1514 PerformReleaseCapture ();
1515 emittingMouseEvent = false;
1516 return handled;
1519 void
1520 Surface::UpdateCursorFromInputList ()
1522 MouseCursor new_cursor = MouseCursorDefault;
1524 // loop over the input list in order until we hit a node that
1525 // has its cursor set to the non-default.
1526 UIElementNode *node;
1527 for (node = (UIElementNode*)input_list->First(); node; node = (UIElementNode*)node->next) {
1528 new_cursor = node->uielement->GetCursor ();
1529 if (new_cursor != MouseCursorDefault)
1530 break;
1533 SetCursor (new_cursor);
1536 void
1537 Surface::SetFPSReportFunc (MoonlightFPSReportFunc report, void *user_data)
1539 fps_report = report;
1540 fps_data = user_data;
1543 void
1544 Surface::SetCacheReportFunc (MoonlightCacheReportFunc report, void *user_data)
1546 cache_report = report;
1547 cache_data = user_data;
1550 void
1551 Surface::SetExposeHandoffFunc (MoonlightExposeHandoffFunc func, void *user_data)
1553 expose_handoff = func;
1554 expose_handoff_data = user_data;
1555 expose_handoff_last_timespan = G_MAXINT64;
1558 class DownloaderNode : public List::Node {
1559 public:
1560 Downloader *downloader;
1561 DownloaderNode (Downloader *dl) { downloader = dl; }
1564 void
1565 Surface::DetachDownloaders ()
1567 DownloaderNode *node;
1568 if (downloaders == NULL)
1569 return;
1571 node = (DownloaderNode *) downloaders->First ();
1572 while (node != NULL) {
1573 node->downloader->RemoveHandler (Downloader::DestroyedEvent, OnDownloaderDestroyed, this);
1574 node->downloader->SetSurface (NULL);
1575 node = (DownloaderNode *) node->next;
1577 downloaders->Clear (true);
1580 void
1581 Surface::OnDownloaderDestroyed (EventObject *sender, EventArgs *args, gpointer closure)
1583 DownloaderNode *node;
1584 Surface *surface = (Surface *) closure;
1585 List *downloaders = surface->downloaders;
1587 if (downloaders == NULL) {
1588 printf ("Surface::OnDownloaderDestroyed (): The list of downloaders is empty.\n");
1589 return;
1592 node = (DownloaderNode *) downloaders->First ();
1593 while (node != NULL) {
1594 if (node->downloader == sender) {
1595 downloaders->Remove (node);
1596 return;
1598 node = (DownloaderNode *) node->next;
1601 printf ("Surface::OnDownloaderDestroyed (): Couldn't find the downloader %p in the list of downloaders\n", sender);
1604 Downloader *
1605 Surface::CreateDownloader (void)
1607 if (zombie) {
1608 g_warning ("Surface::CreateDownloader (): Trying to create a downloader on a zombified surface.\n");
1609 return NULL;
1612 Downloader *downloader = new Downloader ();
1613 downloader->SetSurface (this);
1614 downloader->SetContext (downloader_context);
1615 downloader->AddHandler (Downloader::DestroyedEvent, OnDownloaderDestroyed, this);
1616 if (downloaders == NULL)
1617 downloaders = new List ();
1618 downloaders->Append (new DownloaderNode (downloader));
1620 return downloader;
1623 Downloader *
1624 Surface::CreateDownloader (EventObject *obj)
1626 Surface *surface = obj ? obj->GetSurface () : NULL;
1628 if (surface == NULL)
1629 surface = Deployment::GetCurrent ()->GetSurface ();
1631 if (surface)
1632 return surface->CreateDownloader ();
1634 g_warning ("Surface::CreateDownloader (%p, ID: %i): Unable to create contextual downloader.\n",
1635 obj, GET_OBJ_ID (obj));
1637 return NULL;
1640 bool
1641 Surface::VerifyWithCacheSizeCounter (int w, int h)
1643 if (! (moonlight_flags & RUNTIME_INIT_USE_SHAPE_CACHE))
1644 return false;
1646 if (cache_size_multiplier == -1)
1647 return false;
1649 if (cache_size_in_bytes + (w * h * cache_size_multiplier) < MAXIMUM_CACHE_SIZE)
1650 return true;
1651 else
1652 return false;
1655 gint64
1656 Surface::AddToCacheSizeCounter (int w, int h)
1658 gint64 new_size = w * h * cache_size_multiplier;
1659 cache_size_in_bytes += new_size;
1660 return new_size;
1663 void
1664 Surface::RemoveFromCacheSizeCounter (gint64 size)
1666 cache_size_in_bytes -= size;
1669 bool
1670 Surface::FullScreenKeyHandled (GdkEventKey *key)
1672 if (!GetFullScreen ())
1673 return false;
1675 // If we're in fullscreen mode no key events are passed through.
1676 // We only handle Esc, to exit fullscreen mode.
1677 if (key->keyval == GDK_Escape)
1678 SetFullScreen (false);
1680 switch (key->keyval) {
1681 case GDK_Down:
1682 case GDK_Up:
1683 case GDK_Left:
1684 case GDK_Right:
1685 case GDK_KP_Space:
1686 case GDK_space:
1687 case GDK_Tab:
1688 case GDK_Page_Down:
1689 case GDK_Page_Up:
1690 case GDK_Home:
1691 case GDK_End:
1692 case GDK_Return:
1693 case GDK_KP_Enter:
1694 return false;
1696 // Explicitly listing GDK_Escape here as it should never bubble up
1697 case GDK_Escape:
1698 default:
1699 return true;
1703 gboolean
1704 Surface::HandleUIFocusIn (GdkEventFocus *event)
1706 if (IsZombie ())
1707 return false;
1709 time_manager->InvokeTickCalls();
1711 if (GetFocusedElement ()) {
1712 List *focus_to_root = ElementPathToRoot (GetFocusedElement ());
1713 EmitEventOnList (UIElement::GotFocusEvent, focus_to_root, (GdkEvent*)event, -1);
1714 delete focus_to_root;
1717 return false;
1720 gboolean
1721 Surface::HandleUIFocusOut (GdkEventFocus *event)
1723 if (IsZombie ())
1724 return false;
1726 time_manager->InvokeTickCalls();
1728 if (GetFocusedElement ()) {
1729 List *focus_to_root = ElementPathToRoot (GetFocusedElement ());
1730 EmitEventOnList (UIElement::LostFocusEvent, focus_to_root, (GdkEvent*)event, -1);
1731 delete focus_to_root;
1734 return false;
1737 gboolean
1738 Surface::HandleUIButtonRelease (GdkEventButton *event)
1740 time_manager->InvokeTickCalls();
1742 if (event->button != 1 && event->button != 3) {
1743 return false;
1746 SetUserInitiatedEvent (true);
1748 if (mouse_event)
1749 gdk_event_free (mouse_event);
1751 mouse_event = gdk_event_copy ((GdkEvent *) event);
1753 HandleMouseEvent (event->button == 1 ? UIElement::MouseLeftButtonUpEvent : UIElement::MouseRightButtonUpEvent,
1754 true, true, true, mouse_event);
1756 UpdateCursorFromInputList ();
1757 SetUserInitiatedEvent (false);
1759 // XXX MS appears to do this here, which is completely stupid.
1760 if (captured)
1761 PerformReleaseCapture ();
1763 return !((moonlight_flags & RUNTIME_INIT_DESKTOP_EXTENSIONS) == 0 && event->button == 3);
1766 gboolean
1767 Surface::HandleUIButtonPress (GdkEventButton *event)
1769 bool handled;
1770 int event_id;
1772 active_window->GrabFocus ();
1774 time_manager->InvokeTickCalls();
1776 if (event->button != 1 && event->button != 3)
1777 return false;
1779 SetUserInitiatedEvent (true);
1781 if (mouse_event)
1782 gdk_event_free (mouse_event);
1784 mouse_event = gdk_event_copy ((GdkEvent *) event);
1786 switch (event->type) {
1787 case GDK_3BUTTON_PRESS:
1788 case GDK_2BUTTON_PRESS:
1789 if (event->button != 1)
1790 return false;
1792 handled = HandleMouseEvent (UIElement::MouseLeftButtonMultiClickEvent, false, false, true, mouse_event);
1793 break;
1794 default:
1795 if (event->button == 1)
1796 event_id = UIElement::MouseLeftButtonDownEvent;
1797 else
1798 event_id = UIElement::MouseRightButtonDownEvent;
1800 handled = HandleMouseEvent (event_id, true, true, true, mouse_event);
1801 break;
1804 UpdateCursorFromInputList ();
1805 SetUserInitiatedEvent (false);
1807 return handled;
1810 gboolean
1811 Surface::HandleUIScroll (GdkEventScroll *event)
1813 time_manager->InvokeTickCalls();
1815 if (mouse_event)
1816 gdk_event_free (mouse_event);
1818 mouse_event = gdk_event_copy ((GdkEvent *) event);
1820 bool handled = false;
1822 handled = HandleMouseEvent (UIElement::MouseWheelEvent, true, true, true, mouse_event);
1824 UpdateCursorFromInputList ();
1826 return handled;
1829 gboolean
1830 Surface::HandleUIMotion (GdkEventMotion *event)
1832 time_manager->InvokeTickCalls();
1834 if (mouse_event)
1835 gdk_event_free (mouse_event);
1837 mouse_event = gdk_event_copy ((GdkEvent *) event);
1839 bool handled = false;
1841 if (event->is_hint) {
1842 #if GTK_CHECK_VERSION(2,12,0)
1843 if (gtk_check_version (2, 12, 0))
1844 gdk_event_request_motions (event);
1845 else
1846 #endif
1848 int ix, iy;
1849 GdkModifierType state;
1850 gdk_window_get_pointer (event->window, &ix, &iy, (GdkModifierType*)&state);
1851 ((GdkEventMotion *) mouse_event)->x = ix;
1852 ((GdkEventMotion *) mouse_event)->y = iy;
1856 handled = HandleMouseEvent (UIElement::MouseMoveEvent, true, true, true, mouse_event);
1857 UpdateCursorFromInputList ();
1859 return handled;
1862 gboolean
1863 Surface::HandleUICrossing (GdkEventCrossing *event)
1865 bool handled;
1867 time_manager->InvokeTickCalls();
1869 /* FIXME Disabling this for now... causes issues in ink journal
1870 GdkWindow *active_gdk_window = active_window->GetGdkWindow ();
1872 if (event->window && event->window != active_window->GetGdkWindow ()) {
1873 g_object_unref (active_gdk_window);
1874 return TRUE;
1875 } else
1876 g_object_unref (active_gdk_window);
1879 if (event->type == GDK_ENTER_NOTIFY) {
1880 if (mouse_event)
1881 gdk_event_free (mouse_event);
1882 mouse_event = gdk_event_copy ((GdkEvent *) event);
1884 handled = HandleMouseEvent (UIElement::MouseMoveEvent, true, true, false, mouse_event);
1886 UpdateCursorFromInputList ();
1888 } else {
1889 // forceably emit MouseLeave on the current input
1890 // list.. the "new" list computed by HandleMouseEvent
1891 // should be the same as the current one since we pass
1892 // in the same x,y but I'm not sure that's something
1893 // we can rely on.
1894 handled = HandleMouseEvent (UIElement::MouseLeaveEvent, false, false, true, mouse_event);
1896 // MS specifies that mouse capture is lost when you mouse out of the control
1897 if (captured)
1898 PerformReleaseCapture ();
1900 // clear out the input list so we emit the right
1901 // events when the pointer reenters the control.
1902 if (!emittingMouseEvent) {
1903 delete input_list;
1904 input_list = new List();
1908 return handled;
1911 void
1912 Surface::GenerateFocusChangeEvents()
1914 while (!focus_changed_events->IsEmpty ()) {
1915 FocusChangedNode *node = (FocusChangedNode *) focus_changed_events->Pop ();
1917 List *el_list;
1918 if (node->lost_focus) {
1919 el_list = ElementPathToRoot (node->lost_focus);
1920 EmitEventOnList (UIElement::LostFocusEvent, el_list, NULL, -1);
1921 delete (el_list);
1924 if (node->got_focus) {
1925 el_list = ElementPathToRoot (node->got_focus);
1926 EmitEventOnList (UIElement::GotFocusEvent, el_list, NULL, -1);
1927 delete (el_list);
1929 delete node;
1933 bool
1934 Surface::FocusElement (UIElement *focused)
1936 if (focused == focused_element)
1937 return true;
1939 focus_changed_events->Push (new FocusChangedNode (focused_element, focused));
1940 focused_element = focused;
1942 if (FirstUserInitiatedEvent ())
1943 AddTickCall (Surface::AutoFocusAsync);
1944 return true;
1947 List*
1948 Surface::ElementPathToRoot (UIElement *source)
1950 List *list = new List();
1951 while (source) {
1952 list->Append (new UIElementNode (source));
1953 source = source->GetVisualParent();
1955 return list;
1958 gboolean
1959 Surface::HandleUIKeyPress (GdkEventKey *event)
1961 time_manager->InvokeTickCalls();
1963 Key key = Keyboard::MapKeyValToKey (event->keyval);
1965 if (Keyboard::IsKeyPressed (key)) {
1966 // If we are running an SL 1.0 application, then key repeats are dropped
1967 Deployment *deployment = Deployment::GetCurrent ();
1968 if (!deployment->IsLoadedFromXap ())
1969 return true;
1970 } else if (FullScreenKeyHandled (event)) {
1971 return true;
1974 #if DEBUG_MARKER_KEY
1975 static int debug_marker_key_in = 0;
1976 if (event->keyval == GDK_d || event->keyval == GDK_D) {
1977 if (!debug_marker_key_in)
1978 printf ("<--- DEBUG MARKER KEY IN (%f) --->\n", get_now () / 10000000.0);
1979 else
1980 printf ("<--- DEBUG MARKER KEY OUT (%f) --->\n", get_now () / 10000000.0);
1981 debug_marker_key_in = ! debug_marker_key_in;
1982 return true;
1984 #endif
1986 SetUserInitiatedEvent (true);
1987 bool handled;
1989 Keyboard::OnKeyPress (key);
1991 if (focused_element) {
1992 List *focus_to_root = ElementPathToRoot (focused_element);
1993 handled = EmitEventOnList (UIElement::KeyDownEvent, focus_to_root, (GdkEvent*)event, -1);
1994 delete focus_to_root;
1996 else if (toplevel){
1997 // in silverlight 1.0, key events are only ever delivered to the toplevel
1998 toplevel->EmitKeyDown (event);
1999 handled = true;
2002 SetUserInitiatedEvent (false);
2004 return handled;
2007 gboolean
2008 Surface::HandleUIKeyRelease (GdkEventKey *event)
2010 time_manager->InvokeTickCalls();
2012 if (FullScreenKeyHandled (event))
2013 return true;
2015 SetUserInitiatedEvent (true);
2016 bool handled;
2018 Key key = Keyboard::MapKeyValToKey (event->keyval);
2019 Keyboard::OnKeyRelease (key);
2021 if (focused_element) {
2022 List *focus_to_root = ElementPathToRoot (focused_element);
2023 handled = EmitEventOnList (UIElement::KeyUpEvent, focus_to_root, (GdkEvent*)event, -1);
2024 delete focus_to_root;
2026 else if (toplevel) {
2027 // in silverlight 1.0, key events are only ever delivered to the toplevel
2028 toplevel->EmitKeyUp (event);
2029 handled = true;
2032 SetUserInitiatedEvent (false);
2034 return handled;
2037 void
2038 Surface::HandleUIWindowAllocation (bool emit_resize)
2040 Realloc ();
2041 if (emit_resize)
2042 Emit (ResizeEvent);
2045 void
2046 Surface::HandleUIWindowDestroyed (MoonWindow *window)
2048 if (window == fullscreen_window) {
2049 // switch out of fullscreen mode, as something has
2050 // destroyed our fullscreen window.
2051 UpdateFullScreen (false);
2053 else if (window == normal_window) {
2054 // something destroyed our normal window
2055 normal_window = NULL;
2058 if (window == active_window)
2059 active_window = NULL;
2062 void
2063 Surface::SetBackgroundColor (Color *color)
2065 if (background_color)
2066 delete background_color;
2068 background_color = new Color (*color);
2070 active_window->SetBackgroundColor (color);
2071 active_window->Invalidate ();
2074 Color *
2075 Surface::GetBackgroundColor ()
2077 return background_color;
2080 bool
2081 Surface::IsVersionSupported (const char *version_list)
2083 /* we support all 0.*, 1.0.*, 1.1.* and 2.0.* versions. */
2084 bool supported = true;
2085 gchar **versions;
2086 char *version = NULL;
2087 gint64 numbers [4];
2089 if (version_list == NULL)
2090 return false;
2092 versions = g_strsplit (version_list, ".", 4);
2094 supported = versions [0] != NULL && versions [1] != NULL;
2096 if (supported) {
2097 for (int k = 0; k < 4; k++) {
2098 numbers [k] = 0;
2099 version = versions [k];
2101 if (version == NULL)
2102 break;
2104 if (version [0] == 0) {
2105 supported = false;
2106 break;
2109 // Only allow ascii 0-9 characters in the numbers
2110 for (int i = 0; version [i] != 0; i++) {
2111 if (version [i] < '0' || version [i] > '9') {
2112 supported = false;
2113 break;
2117 numbers [k] = atoll (version);
2120 switch (numbers [0]) {
2121 case 0: // We support all versions of the format "0.*" and "1.*"
2122 case 1:
2123 break;
2124 case 2:
2125 supported &= numbers [1] == 0; // 2.0.*
2126 break;
2127 default:
2128 supported = false;
2129 break;
2133 g_strfreev (versions);
2135 return supported;
2138 void
2139 runtime_init_browser (const char *plugin_dir)
2141 runtime_init (plugin_dir, RUNTIME_INIT_BROWSER);
2144 void
2145 runtime_init_desktop ()
2147 runtime_init (NULL, RUNTIME_INIT_DESKTOP);
2150 static gint32
2151 get_flags (gint32 def, const char *envname, struct env_options options[])
2153 gint32 flags = def;
2154 const char *env;
2156 if (envname && (env = g_getenv (envname))) {
2157 printf ("%s = %s\n", envname, env);
2159 const char *flag = env;
2160 const char *inptr;
2161 bool all = !strcmp ("all", env);
2162 size_t n;
2163 uint i;
2165 while (*flag == ',')
2166 flag++;
2168 inptr = flag;
2170 while (*flag) {
2171 while (*inptr && *inptr != ',')
2172 inptr++;
2174 n = (inptr - flag);
2175 for (i = 0; options[i].name != NULL; i++) {
2176 if (all) {
2177 flags |= options[i].flag;
2178 continue;
2181 if (n != strlen (options[i].name))
2182 continue;
2184 if (!strncmp (options[i].name, flag, n)) {
2185 if (!options[i].set)
2186 flags &= ~options[i].flag;
2187 else
2188 flags |= options[i].flag;
2192 while (*inptr == ',')
2193 inptr++;
2195 flag = inptr;
2198 return flags;
2201 void
2202 runtime_init (const char *platform_dir, guint32 flags)
2204 if (inited)
2205 return;
2207 if (cairo_version () < CAIRO_VERSION_ENCODE(1,4,0)) {
2208 printf ("*** WARNING ***\n");
2209 printf ("*** Cairo versions < 1.4.0 should not be used for Moon.\n");
2210 printf ("*** Moon is being run against version %s.\n", cairo_version_string ());
2211 printf ("*** Proceed at your own risk\n");
2214 if (running_on_nvidia ()) {
2215 printf ("Moonlight: Forcing client-side rendering because we detected binary drivers which are known to suffer performance problems.\n");
2216 flags &= ~RUNTIME_INIT_USE_BACKEND_XLIB;
2219 // Allow the user to override the flags via his/her environment
2220 flags = get_flags (flags, "MOONLIGHT_OVERRIDES", overrides);
2221 #if DEBUG
2222 debug_flags_ex = get_flags (0, "MOONLIGHT_DEBUG", debug_extras);
2223 debug_flags = get_flags (0, "MOONLIGHT_DEBUG", debugs);
2224 #endif
2226 inited = true;
2228 if (!g_type_inited) {
2229 g_type_inited = true;
2230 g_type_init ();
2233 moonlight_flags = flags;
2235 Deployment::Initialize (platform_dir, (flags & RUNTIME_INIT_CREATE_ROOT_DOMAIN) != 0);
2237 xaml_init ();
2238 downloader_init ();
2239 Media::Initialize ();
2242 void
2243 runtime_shutdown (void)
2245 if (!inited)
2246 return;
2248 Media::Shutdown ();
2250 inited = false;