xref: /haiku/src/servers/app/drawing/interface/virtual/ViewHWInterface.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2001-2009, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		DarkWyrm <bpmagic@columbus.rr.com>
7  *		Stephan Aßmus <superstippi@gmx.de>
8  */
9 
10 
11 /*!	BView/BWindow combination HWInterface implementation */
12 
13 
14 #include "ViewHWInterface.h"
15 
16 #include <new>
17 #include <stdio.h>
18 
19 #include <Application.h>
20 #include <Bitmap.h>
21 #include <Cursor.h>
22 #include <Locker.h>
23 #include <Message.h>
24 #include <MessageFilter.h>
25 #include <MessageRunner.h>
26 #include <Region.h>
27 #include <Screen.h>
28 #include <String.h>
29 #include <View.h>
30 #include <Window.h>
31 
32 #include <ServerProtocol.h>
33 
34 #include "BBitmapBuffer.h"
35 #include "PortLink.h"
36 #include "ServerConfig.h"
37 #include "ServerCursor.h"
38 #include "UpdateQueue.h"
39 
40 
41 #ifdef DEBUG_DRIVER_MODULE
42 #	include <stdio.h>
43 #	define STRACE(x) printf x
44 #else
45 #	define STRACE(x) ;
46 #endif
47 
48 
49 const unsigned char kEmptyCursor[] = { 16, 1, 0, 0,
50 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
51 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
52 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
54 
55 static const bool kDefaultDoubleBuffered = true;
56 
57 enum {
58 	MSG_UPDATE = 'updt'
59 };
60 
61 
62 const char*
63 string_for_color_space(color_space format)
64 {
65 	const char* name = "<unkown format>";
66 	switch (format) {
67 		case B_RGBA64:
68 			name = "B_RGBA64";
69 			break;
70 		case B_RGBA64_BIG:
71 			name = "B_RGBA64_BIG";
72 			break;
73 		case B_RGB48:
74 			name = "B_RGB48";
75 			break;
76 		case B_RGB48_BIG:
77 			name = "B_RGB48_BIG";
78 			break;
79 		case B_RGB32:
80 			name = "B_RGB32";
81 			break;
82 		case B_RGBA32:
83 			name = "B_RGBA32";
84 			break;
85 		case B_RGB32_BIG:
86 			name = "B_RGB32_BIG";
87 			break;
88 		case B_RGBA32_BIG:
89 			name = "B_RGBA32_BIG";
90 			break;
91 		case B_RGB24:
92 			name = "B_RGB24";
93 			break;
94 		case B_RGB24_BIG:
95 			name = "B_RGB24_BIG";
96 			break;
97 		case B_CMAP8:
98 			name = "B_CMAP8";
99 			break;
100 		case B_GRAY8:
101 			name = "B_GRAY8";
102 			break;
103 		case B_GRAY1:
104 			name = "B_GRAY1";
105 			break;
106 		default:
107 			break;
108 	}
109 	return name;
110 }
111 
112 
113 static int32
114 run_app_thread(void* cookie)
115 {
116 	if (BApplication* app = (BApplication*)cookie) {
117 		app->Lock();
118 		app->Run();
119 		delete app;
120 	}
121 	return 0;
122 }
123 
124 
125 //#define INPUTSERVER_TEST_MODE 1
126 
127 
128 class CardView : public BView {
129 public:
130 								CardView(BRect bounds);
131 	virtual						~CardView();
132 
133 	virtual	void				AttachedToWindow();
134 	virtual	void				Draw(BRect updateRect);
135 	virtual	void				MessageReceived(BMessage* message);
136 
137 								// CardView
138 			void				SetBitmap(const BBitmap* bitmap);
139 
140 			void				ForwardMessage(BMessage* message = NULL);
141 
142 private:
143 			port_id				fInputPort;
144 			const BBitmap*		fBitmap;
145 };
146 
147 class CardWindow : public BWindow {
148 public:
149 								CardWindow(BRect frame);
150 	virtual						~CardWindow();
151 
152 	virtual	void				MessageReceived(BMessage* message);
153 	virtual	bool				QuitRequested();
154 
155 								// CardWindow
156 			void				SetBitmap(const BBitmap* bitmap);
157 			void				Invalidate(const BRect& area);
158 
159 private:
160 			CardView*			fView;
161 			BRegion				fUpdateRegion;
162 			BLocker				fUpdateLock;
163 };
164 
165 class CardMessageFilter : public BMessageFilter {
166 public:
167 								CardMessageFilter(CardView* view);
168 
169 	virtual filter_result		Filter(BMessage* message, BHandler** _target);
170 
171 private:
172 			CardView*			fView;
173 };
174 
175 
176 //	#pragma mark -
177 
178 
179 CardView::CardView(BRect bounds)
180 	:
181 	BView(bounds, "graphics card view", B_FOLLOW_ALL, B_WILL_DRAW),
182 	fBitmap(NULL)
183 {
184 	SetViewColor(B_TRANSPARENT_32_BIT);
185 
186 #ifndef INPUTSERVER_TEST_MODE
187 	fInputPort = create_port(200, SERVER_INPUT_PORT);
188 #else
189 	fInputPort = create_port(100, "ViewInputDevice");
190 #endif
191 
192 #ifdef ENABLE_INPUT_SERVER_EMULATION
193 	AddFilter(new CardMessageFilter(this));
194 #endif
195 }
196 
197 
198 CardView::~CardView()
199 {
200 }
201 
202 
203 void
204 CardView::AttachedToWindow()
205 {
206 }
207 
208 
209 void
210 CardView::Draw(BRect updateRect)
211 {
212 	if (fBitmap != NULL)
213 		DrawBitmapAsync(fBitmap, updateRect, updateRect);
214 }
215 
216 
217 /*!	These functions emulate the Input Server by sending the *exact* same kind of
218 	messages to the server's port. Being we're using a regular window, it would
219 	make little sense to do anything else.
220 */
221 void
222 CardView::ForwardMessage(BMessage* message)
223 {
224 	if (message == NULL)
225 		message = Window()->CurrentMessage();
226 	if (message == NULL)
227 		return;
228 
229 	// remove some fields that potentially mess up our own message processing
230 	BMessage copy = *message;
231 	copy.RemoveName("screen_where");
232 	copy.RemoveName("be:transit");
233 	copy.RemoveName("be:view_where");
234 	copy.RemoveName("be:cursor_needed");
235 	copy.RemoveName("_view_token");
236 
237 	size_t length = copy.FlattenedSize();
238 	char stream[length];
239 
240 	if (copy.Flatten(stream, length) == B_OK)
241 		write_port(fInputPort, 0, stream, length);
242 }
243 
244 
245 void
246 CardView::MessageReceived(BMessage* message)
247 {
248 	switch (message->what) {
249 		default:
250 			BView::MessageReceived(message);
251 			break;
252 	}
253 }
254 
255 
256 void
257 CardView::SetBitmap(const BBitmap* bitmap)
258 {
259 	if (bitmap != fBitmap) {
260 		fBitmap = bitmap;
261 
262 		if (Parent())
263 			Invalidate();
264 	}
265 }
266 
267 
268 //	#pragma mark -
269 
270 
271 CardMessageFilter::CardMessageFilter(CardView* view)
272 	:
273 	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
274 	fView(view)
275 {
276 }
277 
278 
279 filter_result
280 CardMessageFilter::Filter(BMessage* message, BHandler** target)
281 {
282 	switch (message->what) {
283 		case B_KEY_DOWN:
284 		case B_UNMAPPED_KEY_DOWN:
285 		case B_KEY_UP:
286 		case B_UNMAPPED_KEY_UP:
287 		case B_MOUSE_DOWN:
288 		case B_MOUSE_UP:
289 		case B_MOUSE_WHEEL_CHANGED:
290 			if (message->what == B_MOUSE_DOWN)
291 				fView->SetMouseEventMask(B_POINTER_EVENTS);
292 
293 			fView->ForwardMessage(message);
294 			return B_SKIP_MESSAGE;
295 
296 		case B_MOUSE_MOVED:
297 		{
298 			int32 transit;
299 			if (message->FindInt32("be:transit", &transit) == B_OK
300 				&& transit == B_ENTERED_VIEW) {
301 				// A bug in R5 prevents this call from having an effect if
302 				// called elsewhere, and calling it here works, if we're lucky :-)
303 				BCursor cursor(kEmptyCursor);
304 				fView->SetViewCursor(&cursor, true);
305 			}
306 			fView->ForwardMessage(message);
307 			return B_SKIP_MESSAGE;
308 		}
309 	}
310 
311 	return B_DISPATCH_MESSAGE;
312 }
313 
314 
315 //	#pragma mark -
316 
317 
318 CardWindow::CardWindow(BRect frame)
319 	:
320 	BWindow(frame, "Haiku App Server", B_TITLED_WINDOW,
321 		B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_NO_SERVER_SIDE_WINDOW_MODIFIERS),
322 	fUpdateRegion(),
323 	fUpdateLock("update lock")
324 {
325 	fView = new CardView(Bounds());
326 	AddChild(fView);
327 	fView->MakeFocus();
328 		// make it receive key events
329 }
330 
331 
332 CardWindow::~CardWindow()
333 {
334 }
335 
336 
337 void
338 CardWindow::MessageReceived(BMessage* msg)
339 {
340 	STRACE("CardWindow::MessageReceived()\n");
341 	switch (msg->what) {
342 		case MSG_UPDATE:
343 			STRACE("MSG_UPDATE\n");
344 			// invalidate all areas in the view that need redrawing
345 			if (fUpdateLock.LockWithTimeout(2000LL) >= B_OK) {
346 /*				int32 count = fUpdateRegion.CountRects();
347 				for (int32 i = 0; i < count; i++) {
348 					fView->Invalidate(fUpdateRegion.RectAt(i));
349 				}*/
350 				BRect frame = fUpdateRegion.Frame();
351 				if (frame.IsValid()) {
352 					fView->Invalidate(frame);
353 //					fView->Invalidate();
354 				}
355 				fUpdateRegion.MakeEmpty();
356 				fUpdateLock.Unlock();
357 			} else {
358 				// see you next time
359 			}
360 			break;
361 		default:
362 			BWindow::MessageReceived(msg);
363 			break;
364 	}
365 	STRACE("CardWindow::MessageReceived() - exit\n");
366 }
367 
368 
369 bool
370 CardWindow::QuitRequested()
371 {
372 	port_id serverport = find_port(SERVER_PORT_NAME);
373 
374 	if (serverport >= 0) {
375 		BPrivate::PortLink link(serverport);
376 		link.StartMessage(B_QUIT_REQUESTED);
377 		link.Flush();
378 	} else
379 		printf("ERROR: couldn't find the app_server's main port!");
380 
381 	// we don't quit on ourself, we let us be Quit()!
382 	return false;
383 }
384 
385 
386 void
387 CardWindow::SetBitmap(const BBitmap* bitmap)
388 {
389 	fView->SetBitmap(bitmap);
390 }
391 
392 
393 void
394 CardWindow::Invalidate(const BRect& frame)
395 {
396 	if (LockWithTimeout(1000000) >= B_OK) {
397 		fView->Invalidate(frame);
398 		Unlock();
399 	}
400 }
401 
402 
403 //	#pragma mark -
404 
405 
406 ViewHWInterface::ViewHWInterface()
407 	:
408 	HWInterface(),
409 	fBackBuffer(NULL),
410 	fFrontBuffer(NULL),
411 	fWindow(NULL)
412 {
413 	SetAsyncDoubleBuffered(kDefaultDoubleBuffered);
414 
415 	fDisplayMode.virtual_width = 640;
416 	fDisplayMode.virtual_height = 480;
417 	fDisplayMode.space = B_RGBA32;
418 }
419 
420 
421 ViewHWInterface::~ViewHWInterface()
422 {
423 	if (fWindow) {
424 		fWindow->Lock();
425 		fWindow->Quit();
426 	}
427 
428 	be_app->Lock();
429 	be_app->Quit();
430 }
431 
432 
433 status_t
434 ViewHWInterface::Initialize()
435 {
436 	return B_OK;
437 }
438 
439 
440 status_t
441 ViewHWInterface::Shutdown()
442 {
443 	return B_OK;
444 }
445 
446 
447 status_t
448 ViewHWInterface::SetMode(const display_mode& mode)
449 {
450 	AutoWriteLocker _(this);
451 
452 	status_t ret = B_OK;
453 	// prevent from doing the unnecessary
454 	if (fBackBuffer.IsSet() && fFrontBuffer.IsSet()
455 		&& fDisplayMode.virtual_width == mode.virtual_width
456 		&& fDisplayMode.virtual_height == mode.virtual_height
457 		&& fDisplayMode.space == mode.space)
458 		return ret;
459 
460 	// check if we support the mode
461 
462 	display_mode* modes;
463 	uint32 modeCount, i;
464 	if (GetModeList(&modes, &modeCount) != B_OK)
465 		return B_NO_MEMORY;
466 
467 	for (i = 0; i < modeCount; i++) {
468 		// we only care for the bare minimum
469 		if (modes[i].virtual_width == mode.virtual_width
470 			&& modes[i].virtual_height == mode.virtual_height
471 			&& modes[i].space == mode.space) {
472 			// take over settings
473 			fDisplayMode = modes[i];
474 			break;
475 		}
476 	}
477 
478 	delete[] modes;
479 
480 	if (i == modeCount)
481 		return B_BAD_VALUE;
482 
483 	BRect frame(0.0, 0.0, fDisplayMode.virtual_width - 1,
484 		fDisplayMode.virtual_height - 1);
485 
486 	// create the window if we don't have one already
487 	if (!fWindow) {
488 		// if the window has not been created yet, the BApplication
489 		// has not been created either, but we need one to display
490 		// a real BWindow in the test environment.
491 		// be_app->Run() needs to be called in another thread
492 
493 		if (be_app == NULL) {
494 			BApplication* app = new BApplication(
495 				"application/x-vnd.Haiku-test-app_server");
496 			app->Unlock();
497 
498 			thread_id appThread = spawn_thread(run_app_thread, "app thread",
499 				B_NORMAL_PRIORITY, app);
500 			if (appThread >= B_OK)
501 				ret = resume_thread(appThread);
502 			else
503 				ret = appThread;
504 
505 			if (ret < B_OK)
506 				return ret;
507 		}
508 
509 		fWindow = new CardWindow(frame.OffsetToCopy(BPoint(50.0, 50.0)));
510 
511 		// fire up the window thread but don't show it on screen yet
512 		fWindow->Hide();
513 		fWindow->Show();
514 	}
515 
516 	if (fWindow->Lock()) {
517 		// just to be save
518 		fWindow->SetBitmap(NULL);
519 
520 		// free and reallocate the bitmaps while the window is locked,
521 		// so that the view does not accidentally draw a freed bitmap
522 		fBackBuffer.Unset();
523 		fFrontBuffer.Unset();
524 
525 		// NOTE: backbuffer is always B_RGBA32, this simplifies the
526 		// drawing backend implementation tremendously for the time
527 		// being. The color space conversion is handled in CopyBackToFront()
528 
529 		// TODO: Above not true anymore for single buffered mode!!!
530 		// -> fall back to double buffer for fDisplayMode.space != B_RGB32
531 		// as intermediate solution...
532 		bool doubleBuffered = true;
533 		if ((color_space)fDisplayMode.space != B_RGB32
534 			&& (color_space)fDisplayMode.space != B_RGBA32)
535 			doubleBuffered = true;
536 
537 		BBitmap* frontBitmap
538 			= new BBitmap(frame, 0, (color_space)fDisplayMode.space);
539 		fFrontBuffer.SetTo(new BBitmapBuffer(frontBitmap));
540 
541 		status_t err = fFrontBuffer->InitCheck();
542 		if (err < B_OK) {
543 			fFrontBuffer.Unset();
544 			ret = err;
545 		}
546 
547 		if (err >= B_OK && doubleBuffered) {
548 			// backbuffer is always B_RGBA32
549 			// since we override IsDoubleBuffered(), the drawing buffer
550 			// is in effect also always B_RGBA32.
551 			BBitmap* backBitmap = new BBitmap(frame, 0, B_RGBA32);
552 			fBackBuffer.SetTo(new BBitmapBuffer(backBitmap));
553 
554 			err = fBackBuffer->InitCheck();
555 			if (err < B_OK) {
556 				fBackBuffer.Unset();
557 				ret = err;
558 			}
559 		}
560 
561 		_NotifyFrameBufferChanged();
562 
563 		if (ret >= B_OK) {
564 			// clear out buffers, alpha is 255 this way
565 			// TODO: maybe this should handle different color spaces in different ways
566 			if (fBackBuffer.IsSet())
567 				memset(fBackBuffer->Bits(), 255, fBackBuffer->BitsLength());
568 			memset(fFrontBuffer->Bits(), 255, fFrontBuffer->BitsLength());
569 
570 			// change the window size and update the bitmap used for drawing
571 			fWindow->ResizeTo(frame.Width(), frame.Height());
572 			fWindow->SetBitmap(fFrontBuffer->Bitmap());
573 		}
574 
575 		// window is hidden when this function is called the first time
576 		if (fWindow->IsHidden())
577 			fWindow->Show();
578 
579 		fWindow->Unlock();
580 	} else {
581 		ret = B_ERROR;
582 	}
583 	return ret;
584 }
585 
586 
587 void
588 ViewHWInterface::GetMode(display_mode* mode)
589 {
590 	if (mode && ReadLock()) {
591 		*mode = fDisplayMode;
592 		ReadUnlock();
593 	}
594 }
595 
596 
597 status_t
598 ViewHWInterface::GetDeviceInfo(accelerant_device_info* info)
599 {
600 	// We really don't have to provide anything here because this is strictly
601 	// a software-only driver, but we'll have some fun, anyway.
602 	if (ReadLock()) {
603 		info->version = 100;
604 		sprintf(info->name, "Haiku, Inc. ViewHWInterface");
605 		sprintf(info->chipset, "Haiku, Inc. Chipset");
606 		sprintf(info->serial_no, "3.14159265358979323846");
607 		info->memory = 134217728;	// 128 MB, not that we really have that much. :)
608 		info->dac_speed = 0xFFFFFFFF;	// *heh*
609 
610 		ReadUnlock();
611 	}
612 
613 	return B_OK;
614 }
615 
616 
617 status_t
618 ViewHWInterface::GetFrameBufferConfig(frame_buffer_config& config)
619 {
620 	if (!fFrontBuffer.IsSet())
621 		return B_ERROR;
622 
623 	config.frame_buffer = fFrontBuffer->Bits();
624 	config.frame_buffer_dma = NULL;
625 	config.bytes_per_row = fFrontBuffer->BytesPerRow();
626 
627 	return B_OK;
628 }
629 
630 
631 status_t
632 ViewHWInterface::GetModeList(display_mode** _modes, uint32* _count)
633 {
634 	AutoReadLocker _(this);
635 
636 #if 1
637 	// setup a whole bunch of different modes
638 	const struct resolution { int32 width, height; } resolutions[] = {
639 		{640, 480}, {800, 600}, {1024, 768}, {1152, 864}, {1280, 960},
640 		{1280, 1024}, {1400, 1050}, {1600, 1200}
641 	};
642 	uint32 resolutionCount = sizeof(resolutions) / sizeof(resolutions[0]);
643 	const uint32 colors[] = {B_CMAP8, B_RGB15, B_RGB16, B_RGB32};
644 	uint32 count = resolutionCount * 4;
645 
646 	display_mode* modes = new(std::nothrow) display_mode[count];
647 	if (modes == NULL)
648 		return B_NO_MEMORY;
649 
650 	*_modes = modes;
651 	*_count = count;
652 
653 	int32 index = 0;
654 	for (uint32 i = 0; i < resolutionCount; i++) {
655 		for (uint32 c = 0; c < 4; c++) {
656 			modes[index].virtual_width = resolutions[i].width;
657 			modes[index].virtual_height = resolutions[i].height;
658 			modes[index].space = colors[c];
659 
660 			modes[index].h_display_start = 0;
661 			modes[index].v_display_start = 0;
662 			modes[index].timing.h_display = resolutions[i].width;
663 			modes[index].timing.v_display = resolutions[i].height;
664 			modes[index].timing.h_total = 22000;
665 			modes[index].timing.v_total = 22000;
666 			modes[index].timing.pixel_clock = ((uint32)modes[index].timing.h_total
667 				* modes[index].timing.v_total * 60) / 1000;
668 			modes[index].flags = B_PARALLEL_ACCESS;
669 
670 			index++;
671 		}
672 	}
673 #else
674 	// support only a single mode, useful
675 	// for testing a specific mode
676 	display_mode *modes = new(nothrow) display_mode[1];
677 	modes[0].virtual_width = 640;
678 	modes[0].virtual_height = 480;
679 	modes[0].space = B_CMAP8;
680 
681 	*_modes = modes;
682 	*_count = 1;
683 #endif
684 
685 	return B_OK;
686 }
687 
688 
689 status_t
690 ViewHWInterface::GetPixelClockLimits(display_mode* mode, uint32* low,
691 	uint32* high)
692 {
693 	return B_ERROR;
694 }
695 
696 
697 status_t
698 ViewHWInterface::GetTimingConstraints(display_timing_constraints* constraints)
699 {
700 	return B_ERROR;
701 }
702 
703 
704 status_t
705 ViewHWInterface::ProposeMode(display_mode* candidate, const display_mode* low,
706 	const display_mode* high)
707 {
708 	// We should be able to get away with this because we're not dealing with
709 	// any specific hardware. This is a Good Thing(TM) because we can support
710 	// any hardware we wish within reasonable expectaions and programmer
711 	// laziness. :P
712 	return B_OK;
713 }
714 
715 
716 status_t
717 ViewHWInterface::SetDPMSMode(uint32 state)
718 {
719 	AutoWriteLocker _(this);
720 
721 	return BScreen().SetDPMS(state);
722 }
723 
724 
725 uint32
726 ViewHWInterface::DPMSMode()
727 {
728 	AutoReadLocker _(this);
729 
730 	return BScreen().DPMSState();
731 }
732 
733 
734 uint32
735 ViewHWInterface::DPMSCapabilities()
736 {
737 	AutoReadLocker _(this);
738 
739 	return BScreen().DPMSCapabilites();
740 }
741 
742 
743 status_t
744 ViewHWInterface::SetBrightness(float brightness)
745 {
746 	AutoReadLocker _(this);
747 
748 	return BScreen().SetBrightness(brightness);
749 }
750 
751 
752 status_t
753 ViewHWInterface::GetBrightness(float* brightness)
754 {
755 	AutoReadLocker _(this);
756 
757 	return BScreen().GetBrightness(brightness);
758 }
759 
760 
761 sem_id
762 ViewHWInterface::RetraceSemaphore()
763 {
764 	return -1;
765 }
766 
767 
768 status_t
769 ViewHWInterface::WaitForRetrace(bigtime_t timeout)
770 {
771 	// Locking shouldn't be necessary here - R5 should handle this for us. :)
772 	BScreen screen;
773 	return screen.WaitForRetrace(timeout);
774 }
775 
776 
777 RenderingBuffer*
778 ViewHWInterface::FrontBuffer() const
779 {
780 	return fFrontBuffer.Get();
781 }
782 
783 
784 RenderingBuffer*
785 ViewHWInterface::BackBuffer() const
786 {
787 	return fBackBuffer.Get();
788 }
789 
790 
791 bool
792 ViewHWInterface::IsDoubleBuffered() const
793 {
794 	if (fFrontBuffer.IsSet())
795 		return fBackBuffer.IsSet();
796 
797 	return false;
798 }
799 
800 
801 status_t
802 ViewHWInterface::Invalidate(const BRect& frame)
803 {
804 	status_t ret = HWInterface::Invalidate(frame);
805 
806 	if (ret >= B_OK && fWindow && !IsDoubleBuffered())
807 		fWindow->Invalidate(frame);
808 	return ret;
809 }
810 
811 
812 status_t
813 ViewHWInterface::CopyBackToFront(const BRect& frame)
814 {
815 	status_t ret = HWInterface::CopyBackToFront(frame);
816 
817 	if (ret >= B_OK && fWindow)
818 		fWindow->Invalidate(frame);
819 	return ret;
820 }
821