set eol-style property
[divverent/darkplaces.git] / cl_gecko.c
1 /* --- 8< --- 8< ---   OffscreenGecko headers   --- >8 --- >8 --- */
2
3 /* OffscreenGecko/defs.h */
4
5 #define OSGK_CLASSTYPE_DEF      struct
6 #define OSGK_CLASSTYPE_REF      struct
7
8 #include <assert.h>
9 #define OSGK_ASSERT(x)  assert(x)
10
11 typedef unsigned int OSGK_GeckoResult;
12
13 #if defined(__cplusplus) || defined(__GNUC__)
14 #  define OSGK_INLINE   inline
15 #elif defined(_MSC_VER)
16 #  define OSGK_INLINE   __inline
17 #else
18 #  define OSGK_INLINE
19 #endif
20
21 /* OffscreenGecko/baseobj.h */
22
23 struct OSGK_BaseObject_s
24 {
25   int reserved;
26 };
27 typedef struct OSGK_BaseObject_s OSGK_BaseObject;
28
29 #define OSGK_DERIVEDTYPE(T)           \
30   typedef struct T ## _s {            \
31     OSGK_BaseObject baseobj;          \
32   } T
33
34 static int (*osgk_addref) (OSGK_BaseObject* obj);
35 static int (*osgk_release) (OSGK_BaseObject* obj);
36
37 static OSGK_INLINE int osgk_addref_real (OSGK_BaseObject* obj)
38 {
39   return osgk_addref (obj);
40 }
41
42 static OSGK_INLINE int osgk_release_real (OSGK_BaseObject* obj)
43 {
44   return osgk_release (obj);
45 }
46
47 #define osgk_addref(obj)    osgk_addref_real (&((obj)->baseobj))
48 #define osgk_release(obj)   osgk_release_real (&((obj)->baseobj))
49
50 /* OffscreenGecko/embedding.h */
51
52 OSGK_DERIVEDTYPE(OSGK_EmbeddingOptions);
53
54 static OSGK_EmbeddingOptions* (*osgk_embedding_options_create) (void);
55 static void (*osgk_embedding_options_add_search_path) (
56   OSGK_EmbeddingOptions* options, const char* path);
57 /*static void (*osgk_embedding_options_add_components_path) (
58   OSGK_EmbeddingOptions* options, const char* path);*/
59 static void (*osgk_embedding_options_set_profile_dir) (
60   OSGK_EmbeddingOptions* options, const char* profileDir,
61   const char* localProfileDir);
62
63 OSGK_DERIVEDTYPE(OSGK_Embedding);
64
65 #define OSGK_API_VERSION    1
66
67 static OSGK_Embedding* (*osgk_embedding_create2) (
68   unsigned int apiVer, OSGK_EmbeddingOptions* options, 
69   OSGK_GeckoResult* geckoResult);
70
71 static OSGK_INLINE OSGK_Embedding* osgk_embedding_create (
72   OSGK_GeckoResult* geckoResult)
73 {
74   return osgk_embedding_create2 (OSGK_API_VERSION, 0, geckoResult);
75 }
76
77 static OSGK_INLINE OSGK_Embedding* osgk_embedding_create_with_options (
78   OSGK_EmbeddingOptions* options, OSGK_GeckoResult* geckoResult)
79 {
80   return osgk_embedding_create2 (OSGK_API_VERSION, options, geckoResult);
81 }
82
83 /*static OSGK_GeckoMem* (*osgk_embedding_get_gecko_mem) (
84   OSGK_Embedding* embedding);*/
85
86 /*static OSGK_ComponentMgr* (*osgk_embedding_get_component_mgr) (
87   OSGK_Embedding* embedding);*/
88
89 /*OSGK_CLASSTYPE_DEF nsIComponentManager;
90 OSGK_CLASSTYPE_DEF nsIComponentRegistrar;
91 OSGK_CLASSTYPE_DEF nsIServiceManager;*/
92
93 /*static OSGK_CLASSTYPE_REF nsIComponentManager* 
94 (*osgk_embedding_get_gecko_component_manager) (OSGK_Embedding* embedding);*/
95 /*static OSGK_CLASSTYPE_REF nsIComponentRegistrar* 
96 (*osgk_embedding_get_gecko_component_registrar) (OSGK_Embedding* embedding);*/
97 /*static OSGK_CLASSTYPE_REF nsIServiceManager* 
98 (*osgk_embedding_get_gecko_service_manager) (OSGK_Embedding* embedding);*/
99
100 enum
101 {
102   jsgPrivileged = 1
103 };
104 /*static int (*osgk_embedding_register_js_global) (
105   OSGK_Embedding* embedding, const char* name, const char* contractID,
106   unsigned int flags, OSGK_String** previousContract,
107   OSGK_GeckoResult* geckoResult);*/
108
109 /*static void (*osgk_embedding_clear_focus*) (OSGK_Embedding* embedding);*/
110 /*void (*osgk_embedding_set_auto_focus) (OSGK_Embedding* embedding, int autoFocus);*/
111 /*static int (*osgk_embedding_get_auto_focus) (OSGK_Embedding* embedding);*/
112
113 /* OffscreenGecko/browser.h */
114 OSGK_DERIVEDTYPE(OSGK_Browser);
115
116 static OSGK_Browser* (*osgk_browser_create) (
117   OSGK_Embedding* embedding, int width, int height);
118 static void (*osgk_browser_navigate) (OSGK_Browser* browser,
119   const char* uri);
120
121 static int (*osgk_browser_query_dirty) (OSGK_Browser* browser);
122 static const unsigned char* (*osgk_browser_lock_data) (
123   OSGK_Browser* browser, int* isDirty);
124 static void (*osgk_browser_unlock_data) (OSGK_Browser* browser,
125   const unsigned char* data);
126
127 typedef enum OSGK_MouseButton
128 {
129   mbLeft, 
130   mbRight, 
131   mbMiddle
132 } OSGK_MouseButton;
133
134 typedef enum OSGK_MouseButtonEventType
135 {
136   meDown,
137   meUp,
138   meDoubleClick
139 } OSGK_MouseButtonEventType;
140
141 static void (*osgk_browser_event_mouse_move) (
142   OSGK_Browser* browser, int x, int y);
143 static void (*osgk_browser_event_mouse_button) (
144   OSGK_Browser* browser, OSGK_MouseButton button, 
145   OSGK_MouseButtonEventType eventType);
146
147 typedef enum OSGK_WheelAxis
148 {
149   waVertical,
150   waHorizontal
151 } OSGK_WheelAxis;
152
153 typedef enum OSGK_WheelDirection
154 {
155   wdPositive,
156   wdNegative,
157   wdPositivePage,
158   wdNegativePage
159 } OSGK_WheelDirection;
160
161 static void (*osgk_browser_event_mouse_wheel) (
162   OSGK_Browser* browser, OSGK_WheelAxis axis, 
163   OSGK_WheelDirection direction);
164
165 typedef enum OSGK_KeyboardEventType
166 {
167   keDown,
168   keUp,
169   kePress
170 } OSGK_KeyboardEventType;
171
172 enum
173 {
174   OSGKKey_First = 0x110000,
175
176   OSGKKey_Backspace = OSGKKey_First,
177   OSGKKey_Tab,
178   OSGKKey_Return,
179   OSGKKey_Shift,
180   OSGKKey_Control,
181   OSGKKey_Alt,
182   OSGKKey_CapsLock,
183   OSGKKey_Escape,
184   OSGKKey_Space,
185   OSGKKey_PageUp,
186   OSGKKey_PageDown,
187   OSGKKey_End,
188   OSGKKey_Home,
189   OSGKKey_Left,
190   OSGKKey_Up,
191   OSGKKey_Right,
192   OSGKKey_Down,
193   OSGKKey_Insert,
194   OSGKKey_Delete,
195   OSGKKey_F1,
196   OSGKKey_F2,
197   OSGKKey_F3,
198   OSGKKey_F4,
199   OSGKKey_F5,
200   OSGKKey_F6,
201   OSGKKey_F7,
202   OSGKKey_F8,
203   OSGKKey_F9,
204   OSGKKey_F10,
205   OSGKKey_F11,
206   OSGKKey_F12,
207   OSGKKey_NumLock,
208   OSGKKey_ScrollLock,
209   OSGKKey_Meta
210 };
211
212 static int (*osgk_browser_event_key) (
213   OSGK_Browser* browser, unsigned int key,
214   OSGK_KeyboardEventType eventType);
215
216 typedef enum OSGK_AntiAliasType
217 {
218   aaNone,
219   aaGray,
220   aaSubpixel
221 } OSGK_AntiAliasType;
222
223 /*static void (*osgk_browser_set_antialias) (
224   OSGK_Browser* browser, OSGK_AntiAliasType aaType);*/
225 /*static OSGK_AntiAliasType (*osgk_browser_get_antialias) (OSGK_Browser* browser);*/
226
227 /*static void (*osgk_browser_focus) (OSGK_Browser* browser);*/
228
229 static void (*osgk_browser_resize) (OSGK_Browser* browser,
230   int width, int height);
231
232 /* --- >8 --- >8 --- End OffscreenGecko headers --- 8< --- 8< --- */
233
234 #include "quakedef.h"
235 #include "cl_dyntexture.h"
236 #include "cl_gecko.h"
237 #include "timing.h"
238
239 #define DEFAULT_GECKO_SIZE        512
240
241 static rtexturepool_t *cl_geckotexturepool;
242 static OSGK_Embedding *cl_geckoembedding;
243
244 struct clgecko_s {
245         qboolean active;
246         char name[ MAX_QPATH + 32 ];
247
248         OSGK_Browser *browser;
249         int width, height;
250         int texWidth, texHeight;
251         
252         rtexture_t *texture;
253 };
254
255 static clgecko_t cl_geckoinstances[ MAX_GECKO_INSTANCES ];
256
257 static dllhandle_t osgk_dll = NULL;
258
259 static clgecko_t * cl_gecko_findunusedinstance( void ) {
260         int i;
261         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
262                 clgecko_t *instance = &cl_geckoinstances[ i ];
263                 if( !instance->active ) {
264                         return instance;
265                 }
266         }
267         if( developer.integer > 0 ) {
268                 Con_Printf( "cl_gecko_findunusedinstance: out of geckos\n" );
269         }
270         return NULL;
271 }
272
273 clgecko_t * CL_Gecko_FindBrowser( const char *name ) {
274         int i;
275
276         if( !name || !*name || strncmp( name, CLGECKOPREFIX, sizeof( CLGECKOPREFIX ) - 1 ) != 0 ) {
277                 if( developer.integer > 0 ) {
278                         Con_Printf( "CL_Gecko_FindBrowser: Bad gecko texture name '%s'!\n", name );
279                 }
280                 return NULL;
281         }
282
283         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
284                 clgecko_t *instance = &cl_geckoinstances[ i ];
285                 if( instance->active && strcmp( instance->name, name ) == 0 ) {
286                         return instance;
287                 }
288         }
289
290         if( developer.integer > 0 ) {
291                 Con_Printf( "CL_Gecko_FindBrowser: No browser named '%s'!\n", name );
292         }
293
294         return NULL;
295 }
296
297 static void cl_gecko_updatecallback( rtexture_t *texture, void* callbackData ) {
298         clgecko_t *instance = callbackData;
299         const unsigned char *data;
300         if( instance->browser ) {
301                 // TODO: OSGK only supports BGRA right now
302                 TIMING_TIMESTATEMENT(data = osgk_browser_lock_data( instance->browser, NULL ));
303                 R_UpdateTexture( texture, data, 0, 0, instance->width, instance->height );
304                 osgk_browser_unlock_data( instance->browser, data );
305         }
306 }
307
308 static void cl_gecko_linktexture( clgecko_t *instance ) {
309         // TODO: assert that instance->texture == NULL
310         instance->texture = R_LoadTexture2D( cl_geckotexturepool, instance->name, 
311                 instance->texWidth, instance->texHeight, NULL, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PERSISTENT, NULL );
312         R_MakeTextureDynamic( instance->texture, cl_gecko_updatecallback, instance );
313         CL_LinkDynTexture( instance->name, instance->texture );
314 }
315
316 static void cl_gecko_unlinktexture( clgecko_t *instance ) {
317         if( instance->texture ) {
318                 CL_UnlinkDynTexture( instance->name );
319                 R_FreeTexture( instance->texture );
320                 instance->texture = NULL;
321         }
322 }
323
324 void CL_Gecko_Resize( clgecko_t *instance, int width, int height ) {
325         int newWidth, newHeight;
326
327         // early out if bad parameters are passed (no resize to a texture size bigger than the original texture size)
328         if( !instance || !instance->browser) {
329                 return;
330         }
331
332         newWidth = CeilPowerOf2( width );
333         newHeight = CeilPowerOf2( height );
334         if ((newWidth != instance->texWidth) || (newHeight != instance->texHeight))
335         {
336                 cl_gecko_unlinktexture( instance );
337                 instance->texWidth = newWidth;
338                 instance->texHeight = newHeight;
339                 cl_gecko_linktexture( instance );
340         }
341         else
342         {
343                 /* The gecko area will only cover a part of the texture; to avoid
344                 'old' pixels bleeding in at the border clear the texture. */
345                 R_ClearTexture( instance->texture );
346         }
347
348         osgk_browser_resize( instance->browser, width, height);
349         instance->width = width;
350         instance->height = height;
351 }
352
353 void CL_Gecko_GetTextureExtent( clgecko_t *instance, float* pwidth, float* pheight )
354 {
355         if( !instance || !instance->browser ) {
356                 return;
357         }
358
359         *pwidth = (float)instance->width / instance->texWidth;
360         *pheight = (float)instance->height / instance->texHeight;
361 }
362
363 #if defined(WIN64)
364 # define XULRUNNER_DIR_SUFFIX   "win64"
365 #elif defined(WIN32)
366 # define XULRUNNER_DIR_SUFFIX   "win32"
367 #elif defined(DP_ARCH) && defined(DP_MACHINE)
368 # define XULRUNNER_DIR_SUFFIX   DP_ARCH "-" DP_MACHINE
369 #endif
370
371 clgecko_t * CL_Gecko_CreateBrowser( const char *name ) {
372         clgecko_t *instance;
373
374         if (!osgk_dll) return NULL;
375
376         // TODO: verify that we dont use a name twice
377         instance = cl_gecko_findunusedinstance();
378         // TODO: assert != NULL
379         
380         if( cl_geckoembedding == NULL ) {
381                 char profile_path [MAX_OSPATH];
382                 OSGK_GeckoResult grc;
383                 OSGK_EmbeddingOptions *options;
384
385                 if( developer.integer > 0 ) {
386                         Con_Printf( "CL_Gecko_CreateBrowser: setting up gecko embedding\n" );
387                 }
388
389                 options = osgk_embedding_options_create();
390         #ifdef XULRUNNER_DIR_SUFFIX
391                 osgk_embedding_options_add_search_path( options, "./xulrunner-" XULRUNNER_DIR_SUFFIX "/" );
392         #endif
393                 osgk_embedding_options_add_search_path( options, "./xulrunner/" );
394                 dpsnprintf (profile_path, sizeof (profile_path), "%s/xulrunner_profile/", fs_gamedir);
395                 osgk_embedding_options_set_profile_dir( options, profile_path, 0 );
396                 cl_geckoembedding = osgk_embedding_create_with_options( options, &grc );
397                 osgk_release( options );
398                 
399                 if( cl_geckoembedding == NULL ) {
400                         Con_Printf( "CL_Gecko_CreateBrowser: Couldn't retrieve gecko embedding object (%.8x)!\n", grc );
401                         return NULL;
402                 } else if( developer.integer > 0 ) {
403                         Con_Printf( "CL_Gecko_CreateBrowser: Embedding set up correctly\n" );
404                 }
405         }
406
407         instance->active = true;
408         strlcpy( instance->name, name, sizeof( instance->name ) );
409         instance->browser = osgk_browser_create( cl_geckoembedding, DEFAULT_GECKO_SIZE, DEFAULT_GECKO_SIZE );
410         if( instance->browser == NULL ) {
411                 Con_Printf( "CL_Gecko_CreateBrowser: Browser object creation failed!\n" );
412         }
413         // TODO: assert != NULL
414
415         instance->width = instance->texWidth = DEFAULT_GECKO_SIZE;
416         instance->height = instance->texHeight = DEFAULT_GECKO_SIZE;
417         cl_gecko_linktexture( instance );
418
419         return instance;
420 }
421
422 void CL_Gecko_DestroyBrowser( clgecko_t *instance ) {
423    if( !instance || !instance->active ) {
424                 return;
425         }
426
427         instance->active = false;
428         cl_gecko_unlinktexture( instance );
429
430         osgk_release( instance->browser );
431         instance->browser = NULL;
432 }
433
434 void CL_Gecko_Frame( void ) {
435         int i;
436         // FIXME: track cl_numgeckoinstances to avoid scanning the entire array?
437         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
438                 clgecko_t *instance = &cl_geckoinstances[ i ];
439                 if( instance->active ) {
440                         if( instance->browser && osgk_browser_query_dirty( instance->browser ) == 1 ) {
441                                 R_MarkDirtyTexture( instance->texture );
442                         }
443                 }
444         }
445 }
446
447 static void cl_gecko_start( void )
448 {
449         int i;
450         cl_geckotexturepool = R_AllocTexturePool();
451
452         // recreate textures on module start
453         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
454                 clgecko_t *instance = &cl_geckoinstances[ i ];
455                 if( instance->active ) {
456                         cl_gecko_linktexture( instance );
457                 }
458         }
459 }
460
461 static void cl_gecko_shutdown( void )
462 {
463         int i;
464         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
465                 clgecko_t *instance = &cl_geckoinstances[ i ];
466                 if( instance->active ) {
467                         cl_gecko_unlinktexture( instance );
468                 }
469         }
470         R_FreeTexturePool( &cl_geckotexturepool );
471 }
472
473 static void cl_gecko_newmap( void )
474 {
475         // DO NOTHING
476 }
477
478 void CL_Gecko_Shutdown( void ) {
479         int i;
480         for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) {
481                 clgecko_t *instance = &cl_geckoinstances[ i ];
482                 if( instance->active ) {
483                         cl_gecko_unlinktexture( instance );
484                 }               
485         }
486
487         if (cl_geckoembedding != NULL)
488         {
489             osgk_release( cl_geckoembedding );
490             cl_geckoembedding = NULL;
491         }
492
493         if (osgk_dll != NULL)
494         {
495             Sys_UnloadLibrary (&osgk_dll);
496         }
497 }
498
499 static void cl_gecko_create_f( void ) {
500         char name[MAX_QPATH];
501
502         if (Cmd_Argc() != 2)
503         {
504                 Con_Print("usage: gecko_create <name>\npcreates a browser (full texture path " CLGECKOPREFIX "<name>)\n");
505                 return;
506         }
507
508         // TODO: use snprintf instead
509         sprintf(name, CLGECKOPREFIX "%s", Cmd_Argv(1));
510         CL_Gecko_CreateBrowser( name );
511 }
512
513 static void cl_gecko_destroy_f( void ) {
514         char name[MAX_QPATH];
515
516         if (Cmd_Argc() != 2)
517         {
518                 Con_Print("usage: gecko_destroy <name>\ndestroys a browser\n");
519                 return;
520         }
521
522         // TODO: use snprintf instead
523         sprintf(name, CLGECKOPREFIX "%s", Cmd_Argv(1));
524         CL_Gecko_DestroyBrowser( CL_Gecko_FindBrowser( name ) );
525 }
526
527 static void cl_gecko_navigate_f( void ) {
528         char name[MAX_QPATH];
529         const char *URI;
530
531         if (Cmd_Argc() != 3)
532         {
533                 Con_Print("usage: gecko_navigate <name> <URI>\nnavigates to a certain URI\n");
534                 return;
535         }
536
537         // TODO: use snprintf instead
538         sprintf(name, CLGECKOPREFIX "%s", Cmd_Argv(1));
539         URI = Cmd_Argv( 2 );
540         CL_Gecko_NavigateToURI( CL_Gecko_FindBrowser( name ), URI );
541 }
542
543 static void cl_gecko_injecttext_f( void ) {
544         char name[MAX_QPATH];
545         const char *text;
546         clgecko_t *instance;
547         const char *p;
548
549         if (Cmd_Argc() < 3)
550         {
551                 Con_Print("usage: gecko_injecttext <name> <text>\ninjects a certain text into the browser\n");
552                 return;
553         }
554
555         // TODO: use snprintf instead
556         sprintf(name, CLGECKOPREFIX "%s", Cmd_Argv(1));
557         instance = CL_Gecko_FindBrowser( name );
558         if( !instance ) {
559                 Con_Printf( "cl_gecko_injecttext_f: gecko instance '%s' couldn't be found!\n", name );
560                 return;
561         }
562
563         text = Cmd_Argv( 2 );
564
565         for( p = text ; *p ; p++ ) {
566                 unsigned key = *p;
567                 switch( key ) {
568                         case ' ':
569                                 key = K_SPACE;
570                                 break;
571                         case '\\':
572                                 key = *++p;
573                                 switch( key ) {
574                                 case 'n':
575                                         key = K_ENTER;
576                                         break;
577                                 case '\0':
578                                         --p;
579                                         key = '\\';
580                                         break;
581                                 }
582                                 break;
583                 }
584
585                 CL_Gecko_Event_Key( instance, key, CLG_BET_PRESS );
586         }
587 }
588
589 static void gl_gecko_movecursor_f( void ) {
590         char name[MAX_QPATH];
591         float x, y;
592
593         if (Cmd_Argc() != 4)
594         {
595                 Con_Print("usage: gecko_movecursor <name> <x> <y>\nmove the cursor to a certain position\n");
596                 return;
597         }
598
599         // TODO: use snprintf instead
600         sprintf(name, CLGECKOPREFIX "%s", Cmd_Argv(1));
601         x = atof( Cmd_Argv( 2 ) );
602         y = atof( Cmd_Argv( 3 ) );
603
604         CL_Gecko_Event_CursorMove( CL_Gecko_FindBrowser( name ), x, y );
605 }
606
607 #undef osgk_addref
608 #undef osgk_release
609
610 static const dllfunction_t osgkFuncs[] =
611 {
612         {"osgk_addref",                             (void **) &osgk_addref},
613         {"osgk_release",                            (void **) &osgk_release},
614         {"osgk_embedding_create2",                  (void **) &osgk_embedding_create2},
615         {"osgk_embedding_options_create",           (void **) &osgk_embedding_options_create},
616         {"osgk_embedding_options_add_search_path",  (void **) &osgk_embedding_options_add_search_path},
617         {"osgk_embedding_options_set_profile_dir",  (void **) &osgk_embedding_options_set_profile_dir},
618         {"osgk_browser_create",                     (void **) &osgk_browser_create},
619         {"osgk_browser_query_dirty",                (void **) &osgk_browser_query_dirty},
620         {"osgk_browser_navigate",                   (void **) &osgk_browser_navigate},
621         {"osgk_browser_lock_data",                  (void **) &osgk_browser_lock_data},
622         {"osgk_browser_unlock_data",                (void **) &osgk_browser_unlock_data},
623         {"osgk_browser_resize",                     (void **) &osgk_browser_resize},
624         {"osgk_browser_event_mouse_move",           (void **) &osgk_browser_event_mouse_move},
625         {"osgk_browser_event_mouse_button",         (void **) &osgk_browser_event_mouse_button},
626         {"osgk_browser_event_mouse_wheel",          (void **) &osgk_browser_event_mouse_wheel},
627         {"osgk_browser_event_key",                  (void **) &osgk_browser_event_key},
628         {NULL, NULL}
629 };
630
631 void CL_Gecko_Init( void )
632 {
633         const char* dllnames [] =
634         {
635         #if defined(WIN64)
636                 "OffscreenGecko64.dll",
637         #elif defined(WIN32)
638                 "OffscreenGecko.dll",
639         #elif defined(MACOSX)
640                 "OffscreenGecko.dylib",
641         #else
642                 "OffscreenGecko.so",
643         #endif
644                 NULL
645         };
646         
647         if (!osgk_dll)
648         {
649                 if (! Sys_LoadLibrary (dllnames, &osgk_dll, osgkFuncs))
650                 {
651                         Con_Printf ("Could not load OffscreenGecko, Gecko support unavailable\n");
652                 }
653         }
654
655         Cmd_AddCommand( "gecko_create", cl_gecko_create_f, "Create a gecko browser instance" );
656         Cmd_AddCommand( "gecko_destroy", cl_gecko_destroy_f, "Destroy a gecko browser instance" );
657         Cmd_AddCommand( "gecko_navigate", cl_gecko_navigate_f, "Navigate a gecko browser to a URI" );
658         Cmd_AddCommand( "gecko_injecttext", cl_gecko_injecttext_f, "Injects text into a browser" );
659         Cmd_AddCommand( "gecko_movecursor", gl_gecko_movecursor_f, "Move the cursor to a certain position" );
660
661         R_RegisterModule( "CL_Gecko", cl_gecko_start, cl_gecko_shutdown, cl_gecko_newmap );
662 }
663
664 void CL_Gecko_NavigateToURI( clgecko_t *instance, const char *URI ) {
665         if( !instance || !instance->browser ) {
666                 return;
667         }
668
669         if( instance->active ) {
670                 osgk_browser_navigate( instance->browser, URI );
671         }
672 }
673
674 void CL_Gecko_Event_CursorMove( clgecko_t *instance, float x, float y ) {
675         // TODO: assert x, y \in [0.0, 1.0]
676         int mappedx, mappedy;
677
678         if( !instance || !instance->browser ) {
679                 return;
680         }
681
682         mappedx = x * instance->width;
683         mappedy = y * instance->height;
684         osgk_browser_event_mouse_move( instance->browser, mappedx, mappedy );
685 }
686
687 typedef struct geckokeymapping_s {
688         keynum_t keycode;
689         unsigned int geckokeycode;
690 } geckokeymapping_t;
691
692 static geckokeymapping_t geckokeymappingtable[] = {
693         { K_BACKSPACE, OSGKKey_Backspace },
694         { K_TAB, OSGKKey_Tab },
695         { K_ENTER, OSGKKey_Return },
696         { K_SHIFT, OSGKKey_Shift },
697         { K_CTRL, OSGKKey_Control },
698         { K_ALT, OSGKKey_Alt },
699         { K_CAPSLOCK, OSGKKey_CapsLock },
700         { K_ESCAPE, OSGKKey_Escape },
701         { K_SPACE, OSGKKey_Space },
702         { K_PGUP, OSGKKey_PageUp },
703         { K_PGDN, OSGKKey_PageDown },
704         { K_END, OSGKKey_End },
705         { K_HOME, OSGKKey_Home },
706         { K_LEFTARROW, OSGKKey_Left },
707         { K_UPARROW, OSGKKey_Up },
708         { K_RIGHTARROW, OSGKKey_Right },
709         { K_DOWNARROW, OSGKKey_Down },
710         { K_INS, OSGKKey_Insert },
711         { K_DEL, OSGKKey_Delete },
712         { K_F1, OSGKKey_F1 },
713         { K_F2, OSGKKey_F2 },
714         { K_F3, OSGKKey_F3 },
715         { K_F4, OSGKKey_F4 },
716         { K_F5, OSGKKey_F5 },
717         { K_F6, OSGKKey_F6 },
718         { K_F7, OSGKKey_F7 },
719         { K_F8, OSGKKey_F8 },
720         { K_F9, OSGKKey_F9 },
721         { K_F10, OSGKKey_F10 },
722         { K_F11, OSGKKey_F11 },
723         { K_F12, OSGKKey_F12 },
724         { K_NUMLOCK, OSGKKey_NumLock },
725         { K_SCROLLOCK, OSGKKey_ScrollLock }
726 };
727
728 qboolean CL_Gecko_Event_Key( clgecko_t *instance, keynum_t key, clgecko_buttoneventtype_t eventtype ) {
729         if( !instance || !instance->browser ) {
730                 return false;
731         }
732
733         // determine whether its a keyboard event
734         if( key < K_OTHERDEVICESBEGIN ) {
735
736                 OSGK_KeyboardEventType mappedtype;
737                 unsigned int mappedkey = key;
738                 
739                 unsigned int i;
740                 // yes! then convert it if necessary!
741                 for( i = 0 ; i < sizeof( geckokeymappingtable ) / sizeof( *geckokeymappingtable ) ; i++ ) {
742                         const geckokeymapping_t * const mapping = &geckokeymappingtable[ i ];
743                         if( key == mapping->keycode ) {
744                                 mappedkey = mapping->geckokeycode;
745                                 break;
746                         }
747                 }
748
749                 // convert the eventtype
750                 // map the type
751                 switch( eventtype ) {
752                 case CLG_BET_DOWN:
753                         mappedtype = keDown;
754                         break;
755                 case CLG_BET_UP:
756                         mappedtype = keUp;
757                         break;
758                 case CLG_BET_DOUBLECLICK:
759                         // TODO: error message
760                         break;
761                 case CLG_BET_PRESS:
762                         mappedtype = kePress;
763                 }
764
765                 return osgk_browser_event_key( instance->browser, mappedkey, mappedtype ) != 0;
766         } else if( K_MOUSE1 <= key && key <= K_MOUSE3 ) {
767                 OSGK_MouseButtonEventType mappedtype;
768                 OSGK_MouseButton mappedbutton;
769
770                 mappedbutton = (OSGK_MouseButton) (mbLeft + (key - K_MOUSE1));
771
772                 switch( eventtype ) {
773                 case CLG_BET_DOWN:
774                         mappedtype = meDown;
775                         break;
776                 case CLG_BET_UP:
777                         mappedtype = meUp;
778                         break;
779                 case CLG_BET_DOUBLECLICK:
780                         mappedtype = meDoubleClick;
781                         break;
782                 case CLG_BET_PRESS:
783                         // hihi, hacky hacky
784                         osgk_browser_event_mouse_button( instance->browser, mappedbutton, meDown );
785                         mappedtype = meUp;
786                         break;
787                 }
788
789                 osgk_browser_event_mouse_button( instance->browser, mappedbutton, mappedtype );
790                 return true;
791         } else if( K_MWHEELUP <= key && key <= K_MWHEELDOWN ) {
792                 if( eventtype == CLG_BET_DOWN )
793                         osgk_browser_event_mouse_wheel( instance->browser, 
794                                 waVertical, (key == K_MWHEELUP) ? wdNegative : wdPositive );
795                 return true;
796         }
797         // TODO: error?
798         return false;
799 }