1/*
2 * Copyright 2009, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Michael Lotz <mmlr@mlotz.ch>
7 *		François Revol <revol@free.fr>
8 */
9
10#include "HTML5HWInterface.h"
11#include "HTML5DrawingEngine.h"
12#include "CanvasEventStream.h"
13#include "CanvasMessage.h"
14
15#include "WebHandler.h"
16#include "WebServer.h"
17#include "WebWorker.h"
18//#include "NetReceiver.h"
19//#include "NetSender.h"
20#include "StreamingRingBuffer.h"
21
22#include "desktop.html.h"
23#include "haiku.js.h"
24
25#include <Autolock.h>
26#include <NetEndpoint.h>
27
28#include <new>
29#include <string.h>
30
31
32#define TRACE(x...)				debug_printf("HTML5HWInterface: "x)
33#define TRACE_ALWAYS(x...)		debug_printf("HTML5HWInterface: "x)
34#define TRACE_ERROR(x...)		debug_printf("HTML5HWInterface: "x)
35
36
37struct callback_info {
38	uint32				token;
39	HTML5HWInterface::CallbackFunction	callback;
40	void*				cookie;
41};
42
43
44HTML5HWInterface::HTML5HWInterface(const char* target)
45	:
46	HWInterface(),
47	fTarget(target),
48	fRemoteHost(NULL),
49	fRemotePort(10900),
50	fIsConnected(false),
51	fProtocolVersion(100),
52	fConnectionSpeed(0),
53	fListenPort(10901),
54	fReceiveEndpoint(NULL),
55	fSendBuffer(NULL),
56	fReceiveBuffer(NULL),
57	fServer(NULL),
58//	fSender(NULL),
59//	fReceiver(NULL),
60	fEventThread(-1),
61	fEventStream(NULL),
62	fCallbackLocker("callback locker")
63{
64	fDisplayMode.virtual_width = 640;
65	fDisplayMode.virtual_height = 480;
66	fDisplayMode.space = B_RGB32;
67
68	//TODO: Cleanup; parse host ??
69	fRemoteHost = strdup(fTarget);
70	if (strncmp(fRemoteHost, "html5:", 6) != 0) {
71		fInitStatus = B_BAD_VALUE;
72		return;
73	}
74	char *portStart = fRemoteHost + 5;//strchr(fRemoteHost + 6, ':');
75	if (portStart != NULL) {
76		portStart[0] = 0;
77		portStart++;
78		if (sscanf(portStart, "%lu", &fRemotePort) != 1) {
79			fInitStatus = B_BAD_VALUE;
80			return;
81		}
82
83		fListenPort = fRemotePort;
84	}
85
86	fReceiveEndpoint = new(std::nothrow) BNetEndpoint();
87	if (fReceiveEndpoint == NULL) {
88		fInitStatus = B_NO_MEMORY;
89		return;
90	}
91
92	fInitStatus = fReceiveEndpoint->Bind(fListenPort);
93	if (fInitStatus != B_OK)
94		return;
95
96	fSendBuffer = new(std::nothrow) StreamingRingBuffer(16 * 1024);
97	if (fSendBuffer == NULL) {
98		fInitStatus = B_NO_MEMORY;
99		return;
100	}
101
102	fInitStatus = fSendBuffer->InitCheck();
103	if (fInitStatus != B_OK)
104		return;
105
106	fReceiveBuffer = new(std::nothrow) StreamingRingBuffer(16 * 1024);
107	if (fReceiveBuffer == NULL) {
108		fInitStatus = B_NO_MEMORY;
109		return;
110	}
111
112	fInitStatus = fReceiveBuffer->InitCheck();
113	if (fInitStatus != B_OK)
114		return;
115
116	fServer = new(std::nothrow) WebServer(fReceiveEndpoint);
117	if (fServer == NULL) {
118		fInitStatus = B_NO_MEMORY;
119		return;
120	}
121
122	WebHandler *handler;
123	handler = new(std::nothrow) WebHandler("output", fSendBuffer);
124	if (handler == NULL) {
125		fInitStatus = B_NO_MEMORY;
126		return;
127	}
128	handler->SetMultipart();
129	handler->SetType("multipart/x-mixed-replace;boundary=x");
130	fServer->AddHandler(handler);
131
132	handler = new(std::nothrow) WebHandler("input", fReceiveBuffer);
133	if (handler == NULL) {
134		fInitStatus = B_NO_MEMORY;
135		return;
136	}
137	fServer->AddHandler(handler);
138
139	handler = new(std::nothrow) WebHandler("desktop.html",
140		new(std::nothrow) BMemoryIO(desktop_html, sizeof(desktop_html) - 1));
141	if (handler == NULL) {
142		fInitStatus = B_NO_MEMORY;
143		return;
144	}
145	fServer->AddHandler(handler);
146
147	handler = new(std::nothrow) WebHandler("haiku.js",
148		new(std::nothrow) BMemoryIO(haiku_js, sizeof(haiku_js) - 1));
149	if (handler == NULL) {
150		fInitStatus = B_NO_MEMORY;
151		return;
152	}
153	fServer->AddHandler(handler);
154
155
156	fEventStream = new(std::nothrow) CanvasEventStream();
157	if (fEventStream == NULL) {
158		fInitStatus = B_NO_MEMORY;
159		return;
160	}
161
162//	fInitStatus = _Connect();
163//	if (fInitStatus != B_OK)
164//		return;
165	fDisplayMode.virtual_width = 800;
166	fDisplayMode.virtual_height = 600;
167
168
169	fEventThread = spawn_thread(_EventThreadEntry, "HTML5 event thread",
170		B_NORMAL_PRIORITY, this);
171	if (fEventThread < 0) {
172		fInitStatus = fEventThread;
173		return;
174	}
175
176	resume_thread(fEventThread);
177}
178
179
180HTML5HWInterface::~HTML5HWInterface()
181{
182//	delete fReceiver;
183	delete fReceiveBuffer;
184
185	delete fSendBuffer;
186//	delete fSender;
187
188	delete fReceiveEndpoint;
189//	delete fSendEndpoint;
190	delete fServer;
191
192	delete fEventStream;
193
194	free(fRemoteHost);
195}
196
197
198status_t
199HTML5HWInterface::Initialize()
200{
201	return fInitStatus;
202}
203
204
205status_t
206HTML5HWInterface::Shutdown()
207{
208	_Disconnect();
209	return B_OK;
210}
211
212
213DrawingEngine*
214HTML5HWInterface::CreateDrawingEngine()
215{
216	return new(std::nothrow) HTML5DrawingEngine(this);
217}
218
219
220EventStream*
221HTML5HWInterface::CreateEventStream()
222{
223	return fEventStream;
224}
225
226
227status_t
228HTML5HWInterface::AddCallback(uint32 token, CallbackFunction callback,
229	void* cookie)
230{
231	BAutolock lock(fCallbackLocker);
232	int32 index = fCallbacks.BinarySearchIndexByKey(token, &_CallbackCompare);
233	if (index >= 0)
234		return B_NAME_IN_USE;
235
236	callback_info* info = new(std::nothrow) callback_info;
237	if (info == NULL)
238		return B_NO_MEMORY;
239
240	info->token = token;
241	info->callback = callback;
242	info->cookie = cookie;
243
244	fCallbacks.AddItem(info, -index - 1);
245	return B_OK;
246}
247
248
249bool
250HTML5HWInterface::RemoveCallback(uint32 token)
251{
252	BAutolock lock(fCallbackLocker);
253	int32 index = fCallbacks.BinarySearchIndexByKey(token, &_CallbackCompare);
254	if (index < 0)
255		return false;
256
257	delete fCallbacks.RemoveItemAt(index);
258	return true;
259}
260
261
262callback_info*
263HTML5HWInterface::_FindCallback(uint32 token)
264{
265	BAutolock lock(fCallbackLocker);
266	return fCallbacks.BinarySearchByKey(token, &_CallbackCompare);
267}
268
269
270int
271HTML5HWInterface::_CallbackCompare(const uint32* key,
272	const callback_info* info)
273{
274	if (info->token == *key)
275		return 0;
276
277	if (info->token < *key)
278		return -1;
279
280	return 1;
281}
282
283
284int32
285HTML5HWInterface::_EventThreadEntry(void* data)
286{
287	return ((HTML5HWInterface*)data)->_EventThread();
288}
289
290
291status_t
292HTML5HWInterface::_EventThread()
293{
294	CanvasMessage message(fReceiveBuffer, fSendBuffer);
295	while (true) {
296		uint16 code;
297		status_t result = message.NextMessage(code);
298		if (result != B_OK) {
299			TRACE_ERROR("failed to read message from receiver: %s\n",
300				strerror(result));
301			return result;
302		}
303
304		TRACE("got message code %u with %lu bytes\n", code, message.DataLeft());
305
306		if (code >= RP_MOUSE_MOVED && code <= RP_MODIFIERS_CHANGED) {
307			// an input event, dispatch to the event stream
308			if (fEventStream->EventReceived(message))
309				continue;
310		}
311
312		switch (code) {
313			case RP_UPDATE_DISPLAY_MODE:
314			{
315				// TODO: implement, we only handle it in the context of the
316				// initial mode setup on connect
317				break;
318			}
319
320			default:
321			{
322				uint32 token;
323				if (message.Read(token) == B_OK) {
324					callback_info* info = _FindCallback(token);
325					if (info != NULL && info->callback(info->cookie, message))
326						break;
327				}
328
329				TRACE_ERROR("unhandled HTML5 event code %u\n", code);
330				break;
331			}
332		}
333	}
334}
335
336
337status_t
338HTML5HWInterface::_Connect()
339{
340#if 0
341	TRACE("connecting to host \"%s\" port %lu\n", fRemoteHost, fRemotePort);
342	status_t result = fSendEndpoint->Connect(fRemoteHost, (uint16)fRemotePort);
343	if (result != B_OK) {
344		TRACE_ERROR("failed to connect to host \"%s\" port %lu\n", fRemoteHost,
345			fRemotePort);
346		return result;
347	}
348
349	CanvasMessage message(fReceiveBuffer, fSendBuffer);
350	message.Start(RP_INIT_CONNECTION);
351	message.Add(fListenPort);
352	result = message.Flush();
353	if (result != B_OK) {
354		TRACE_ERROR("failed to send init connection message\n");
355		return result;
356	}
357
358	uint16 code;
359	result = message.NextMessage(code);
360	if (result != B_OK) {
361		TRACE_ERROR("failed to read message from receiver: %s\n",
362			strerror(result));
363		return result;
364	}
365
366	TRACE("code %u with %lu bytes of data\n", code, message.DataLeft());
367	if (code != RP_UPDATE_DISPLAY_MODE) {
368		TRACE_ERROR("invalid connection init code %u\n", code);
369		return B_ERROR;
370	}
371
372	int32 width, height;
373	message.Read(width);
374	result = message.Read(height);
375	if (result != B_OK) {
376		TRACE_ERROR("failed to get initial display mode\n");
377		return result;
378	}
379
380	fDisplayMode.virtual_width = width;
381	fDisplayMode.virtual_height = height;
382#endif
383	return B_OK;
384}
385
386
387void
388HTML5HWInterface::_Disconnect()
389{
390	if (fIsConnected) {
391		CanvasMessage message(NULL, fSendBuffer);
392		message.Start(RP_CLOSE_CONNECTION);
393		message.Flush();
394		fIsConnected = false;
395	}
396
397	if (fReceiveEndpoint != NULL)
398		fReceiveEndpoint->Close();
399}
400
401
402status_t
403HTML5HWInterface::SetMode(const display_mode& mode)
404{
405	// The display mode depends on the screen resolution of the client, we
406	// don't allow to change it.
407	return B_UNSUPPORTED;
408}
409
410
411void
412HTML5HWInterface::GetMode(display_mode* mode)
413{
414	if (mode == NULL || !ReadLock())
415		return;
416
417	*mode = fDisplayMode;
418	ReadUnlock();
419}
420
421
422status_t
423HTML5HWInterface::GetDeviceInfo(accelerant_device_info* info)
424{
425	if (!ReadLock())
426		return B_ERROR;
427
428	info->version = fProtocolVersion;
429	info->dac_speed = fConnectionSpeed;
430	info->memory = 33554432; // 32MB
431	snprintf(info->name, sizeof(info->name), "Haiku, Inc. HTML5HWInterface");
432	snprintf(info->chipset, sizeof(info->chipset), "Haiku, Inc. Chipset");
433	snprintf(info->serial_no, sizeof(info->serial_no), fTarget);
434
435	ReadUnlock();
436	return B_OK;
437}
438
439
440status_t
441HTML5HWInterface::GetFrameBufferConfig(frame_buffer_config& config)
442{
443	// We don't actually have a frame buffer.
444	return B_UNSUPPORTED;
445}
446
447
448status_t
449HTML5HWInterface::GetModeList(display_mode** _modes, uint32* _count)
450{
451	AutoReadLocker _(this);
452
453	display_mode* modes = new(std::nothrow) display_mode[1];
454	if (modes == NULL)
455		return B_NO_MEMORY;
456
457	modes[0] = fDisplayMode;
458	*_modes = modes;
459	*_count = 1;
460	return B_OK;
461}
462
463
464status_t
465HTML5HWInterface::GetPixelClockLimits(display_mode* mode, uint32* low,
466	uint32* high)
467{
468	return B_UNSUPPORTED;
469}
470
471
472status_t
473HTML5HWInterface::GetTimingConstraints(display_timing_constraints* constraints)
474{
475	return B_UNSUPPORTED;
476}
477
478
479status_t
480HTML5HWInterface::ProposeMode(display_mode* candidate, const display_mode* low,
481	const display_mode* high)
482{
483	return B_UNSUPPORTED;
484}
485
486
487status_t
488HTML5HWInterface::SetDPMSMode(uint32 state)
489{
490	return B_UNSUPPORTED;
491}
492
493
494uint32
495HTML5HWInterface::DPMSMode()
496{
497	return (IsConnected() ? B_DPMS_ON : B_DPMS_SUSPEND);
498}
499
500
501uint32
502HTML5HWInterface::DPMSCapabilities()
503{
504	return B_DPMS_ON | B_DPMS_SUSPEND;
505}
506
507
508sem_id
509HTML5HWInterface::RetraceSemaphore()
510{
511	return -1;
512}
513
514
515status_t
516HTML5HWInterface::WaitForRetrace(bigtime_t timeout)
517{
518	return B_UNSUPPORTED;
519}
520
521
522void
523HTML5HWInterface::SetCursor(ServerCursor* cursor)
524{
525	HWInterface::SetCursor(cursor);
526	if (!IsConnected())
527		return;
528	CanvasMessage message(NULL, fSendBuffer);
529	message.Start(RP_SET_CURSOR);
530	message.AddCursor(Cursor().Get());
531}
532
533
534void
535HTML5HWInterface::SetCursorVisible(bool visible)
536{
537	HWInterface::SetCursorVisible(visible);
538	if (!IsConnected())
539		return;
540	CanvasMessage message(NULL, fSendBuffer);
541	message.Start(RP_SET_CURSOR_VISIBLE);
542	message.Add(visible);
543}
544
545
546void
547HTML5HWInterface::MoveCursorTo(float x, float y)
548{
549	HWInterface::MoveCursorTo(x, y);
550	if (!IsConnected())
551		return;
552	CanvasMessage message(NULL, fSendBuffer);
553	message.Start(RP_MOVE_CURSOR_TO);
554	message.Add(x);
555	message.Add(y);
556}
557
558
559void
560HTML5HWInterface::SetDragBitmap(const ServerBitmap* bitmap,
561	const BPoint& offsetFromCursor)
562{
563	HWInterface::SetDragBitmap(bitmap, offsetFromCursor);
564	if (!IsConnected())
565		return;
566	CanvasMessage message(NULL, fSendBuffer);
567	message.Start(RP_SET_CURSOR);
568	message.AddCursor(CursorAndDragBitmap().Get());
569}
570
571
572RenderingBuffer*
573HTML5HWInterface::FrontBuffer() const
574{
575	return NULL;
576}
577
578
579RenderingBuffer*
580HTML5HWInterface::BackBuffer() const
581{
582	return NULL;
583}
584
585
586bool
587HTML5HWInterface::IsDoubleBuffered() const
588{
589	return false;
590}
591
592
593status_t
594HTML5HWInterface::InvalidateRegion(BRegion& region)
595{
596	CanvasMessage message(NULL, fSendBuffer);
597	if (!IsConnected())
598		return B_OK;
599	message.Start(RP_INVALIDATE_REGION);
600	message.AddRegion(region);
601	return B_OK;
602}
603
604
605status_t
606HTML5HWInterface::Invalidate(const BRect& frame)
607{
608	CanvasMessage message(NULL, fSendBuffer);
609	if (!IsConnected())
610		return B_OK;
611	message.Start(RP_INVALIDATE_RECT);
612	message.Add(frame);
613	return B_OK;
614}
615
616
617status_t
618HTML5HWInterface::CopyBackToFront(const BRect& frame)
619{
620	return B_OK;
621}
622