1/* Copyright (C) 2006-2010 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12
13#include "android/utils/debug.h"
14#include "android/utils/bufprint.h"
15#include "android/globals.h"
16#include "android/qemulator.h"
17#include "android/protocol/core-commands-api.h"
18#include "android/protocol/ui-commands-api.h"
19#include "user-events.h"
20
21#define  D(...)  do {  if (VERBOSE_CHECK(init)) dprint(__VA_ARGS__); } while (0)
22static double get_default_scale( AndroidOptions*  opts );
23
24/* QEmulator structure instance. */
25static QEmulator   qemulator[1];
26
27static void handle_key_command( void*  opaque, SkinKeyCommand  command, int  param );
28static void qemulator_refresh(QEmulator* emulator);
29extern void qemu_system_shutdown_request(void);
30
31static void
32qemulator_light_brightness( void* opaque, const char*  light, int  value )
33{
34    QEmulator*  emulator = opaque;
35
36    VERBOSE_PRINT(hw_control,"%s: light='%s' value=%d window=%p", __FUNCTION__, light, value, emulator->window);
37    if ( !strcmp(light, "lcd_backlight") ) {
38        emulator->lcd_brightness = value;
39        if (emulator->window)
40            skin_window_set_lcd_brightness( emulator->window, value );
41        return;
42    }
43}
44
45static void
46qemulator_setup( QEmulator*  emulator )
47{
48    AndroidOptions*  opts = emulator->opts;
49
50    if ( !emulator->window && !opts->no_window ) {
51        SkinLayout*  layout = emulator->layout;
52        double       scale  = get_default_scale(emulator->opts);
53
54        emulator->window = skin_window_create( layout, emulator->win_x, emulator->win_y, scale, 0);
55        if (emulator->window == NULL)
56            return;
57
58        {
59            SkinTrackBall*           ball;
60            SkinTrackBallParameters  params;
61
62            params.diameter   = 30;
63            params.ring       = 2;
64            params.ball_color = 0xffe0e0e0;
65            params.dot_color  = 0xff202020;
66            params.ring_color = 0xff000000;
67
68            ball = skin_trackball_create( &params );
69            emulator->trackball = ball;
70            skin_window_set_trackball( emulator->window, ball );
71
72            emulator->lcd_brightness = 128;  /* 50% */
73            skin_window_set_lcd_brightness( emulator->window, emulator->lcd_brightness );
74        }
75
76        if ( emulator->onion != NULL )
77            skin_window_set_onion( emulator->window,
78                                   emulator->onion,
79                                   emulator->onion_rotation,
80                                   emulator->onion_alpha );
81
82        qemulator_set_title(emulator);
83
84        skin_window_enable_touch ( emulator->window,
85                                   !androidHwConfig_isScreenNoTouch(android_hw));
86        skin_window_enable_dpad  ( emulator->window, android_hw->hw_dPad != 0 );
87        skin_window_enable_qwerty( emulator->window, android_hw->hw_keyboard != 0 );
88        skin_window_enable_trackball( emulator->window, android_hw->hw_trackBall != 0 );
89    }
90
91    /* initialize hardware control support */
92    uicmd_set_brightness_change_callback(qemulator_light_brightness,
93                                         emulator);
94}
95
96static void
97qemulator_fb_update( void*   _emulator, int  x, int  y, int  w, int  h )
98{
99    QEmulator*  emulator = _emulator;
100
101    if (!emulator->window) {
102        if (emulator->opts->no_window)
103            return;
104        qemulator_setup( emulator );
105    }
106    skin_window_update_display( emulator->window, x, y, w, h );
107}
108
109static void
110qemulator_fb_rotate( void*  _emulator, int  rotation )
111{
112    QEmulator*  emulator = _emulator;
113
114    qemulator_setup( emulator );
115}
116
117static void
118qemulator_fb_poll( void* _emulator )
119{
120    QEmulator* emulator = _emulator;
121    qemulator_refresh(emulator);
122}
123
124QEmulator*
125qemulator_get(void)
126{
127    return qemulator;
128}
129
130int
131qemulator_init( QEmulator*       emulator,
132                AConfig*         aconfig,
133                const char*      basepath,
134                int              x,
135                int              y,
136                AndroidOptions*  opts )
137{
138    emulator->aconfig     = aconfig;
139    emulator->layout_file = skin_file_create_from_aconfig(aconfig, basepath);
140    emulator->layout      = emulator->layout_file->layouts;
141    emulator->keyboard    = skin_keyboard_create(opts->charmap, opts->raw_keys);
142    emulator->window      = NULL;
143    emulator->win_x       = x;
144    emulator->win_y       = y;
145    emulator->opts[0]     = opts[0];
146
147    /* register as a framebuffer clients for all displays defined in the skin file */
148    SKIN_FILE_LOOP_PARTS( emulator->layout_file, part )
149        SkinDisplay*  disp = part->display;
150        if (disp->valid) {
151            qframebuffer_add_client( disp->qfbuff,
152                                     emulator,
153                                     qemulator_fb_update,
154                                     qemulator_fb_rotate,
155                                     qemulator_fb_poll,
156                                     NULL );
157        }
158    SKIN_FILE_LOOP_END_PARTS
159
160    skin_keyboard_enable( emulator->keyboard, 1 );
161    skin_keyboard_on_command( emulator->keyboard, handle_key_command, emulator );
162
163    return 0;
164}
165
166void
167qemulator_done(QEmulator* emulator)
168{
169    if (emulator->window) {
170        skin_window_free(emulator->window);
171        emulator->window = NULL;
172    }
173    if (emulator->trackball) {
174        skin_trackball_destroy(emulator->trackball);
175        emulator->trackball = NULL;
176    }
177    if (emulator->keyboard) {
178        skin_keyboard_free(emulator->keyboard);
179        emulator->keyboard = NULL;
180    }
181    emulator->layout = NULL;
182    if (emulator->layout_file) {
183        skin_file_free(emulator->layout_file);
184        emulator->layout_file = NULL;
185    }
186}
187
188SkinLayout*
189qemulator_get_layout(QEmulator* emulator)
190{
191    return emulator->layout;
192}
193
194QFrameBuffer*
195qemulator_get_first_framebuffer(QEmulator* emulator)
196{
197    /* register as a framebuffer clients for all displays defined in the skin file */
198    SKIN_FILE_LOOP_PARTS( emulator->layout_file, part )
199        SkinDisplay*  disp = part->display;
200        if (disp->valid) {
201            return disp->qfbuff;
202        }
203    SKIN_FILE_LOOP_END_PARTS
204    return NULL;
205}
206
207void
208qemulator_set_title(QEmulator* emulator)
209{
210    char  temp[128], *p=temp, *end=p+sizeof temp;;
211
212    if (emulator->window == NULL)
213        return;
214
215    if (emulator->show_trackball) {
216        SkinKeyBinding  bindings[ SKIN_KEY_COMMAND_MAX_BINDINGS ];
217        int             count;
218
219        count = skin_keyset_get_bindings( android_keyset,
220                                          SKIN_KEY_COMMAND_TOGGLE_TRACKBALL,
221                                          bindings );
222
223        if (count > 0) {
224            int  nn;
225            p = bufprint( p, end, "Press " );
226            for (nn = 0; nn < count; nn++) {
227                if (nn > 0) {
228                    if (nn < count-1)
229                        p = bufprint(p, end, ", ");
230                    else
231                        p = bufprint(p, end, " or ");
232                }
233                p = bufprint(p, end, "%s",
234                             skin_key_symmod_to_str( bindings[nn].sym,
235                                                     bindings[nn].mod ) );
236            }
237            p = bufprint(p, end, " to leave trackball mode. ");
238        }
239    }
240
241    p = bufprint(p, end, "%d:%s",
242                 android_base_port,
243                 avdInfo_getName( android_avdInfo ));
244
245    skin_window_set_title( emulator->window, temp );
246}
247
248/*
249 * Helper routines
250 */
251
252static int
253get_device_dpi( AndroidOptions*  opts )
254{
255    int    dpi_device  = corecmd_get_hw_lcd_density();
256
257    if (opts->dpi_device != NULL) {
258        char*  end;
259        dpi_device = strtol( opts->dpi_device, &end, 0 );
260        if (end == NULL || *end != 0 || dpi_device <= 0) {
261            fprintf(stderr, "argument for -dpi-device must be a positive integer. Aborting\n" );
262            exit(1);
263        }
264    }
265    return  dpi_device;
266}
267
268static double
269get_default_scale( AndroidOptions*  opts )
270{
271    int     dpi_device  = get_device_dpi( opts );
272    int     dpi_monitor = -1;
273    double  scale       = 0.0;
274
275    /* possible values for the 'scale' option are
276     *   'auto'        : try to determine the scale automatically
277     *   '<number>dpi' : indicates the host monitor dpi, compute scale accordingly
278     *   '<fraction>'  : use direct scale coefficient
279     */
280
281    if (opts->scale) {
282        if (!strcmp(opts->scale, "auto"))
283        {
284            /* we need to get the host dpi resolution ? */
285            int   xdpi, ydpi;
286
287            if ( SDL_WM_GetMonitorDPI( &xdpi, &ydpi ) < 0 ) {
288                fprintf(stderr, "could not get monitor DPI resolution from system. please use -dpi-monitor to specify one\n" );
289                exit(1);
290            }
291            D( "system reported monitor resolutions: xdpi=%d ydpi=%d\n", xdpi, ydpi);
292            dpi_monitor = (xdpi + ydpi+1)/2;
293        }
294        else
295        {
296            char*   end;
297            scale = strtod( opts->scale, &end );
298
299            if (end && end[0] == 'd' && end[1] == 'p' && end[2] == 'i' && end[3] == 0) {
300                if ( scale < 20 || scale > 1000 ) {
301                    fprintf(stderr, "emulator: ignoring bad -scale argument '%s': %s\n", opts->scale,
302                            "host dpi number must be between 20 and 1000" );
303                    exit(1);
304                }
305                dpi_monitor = scale;
306                scale       = 0.0;
307            }
308            else if (end == NULL || *end != 0) {
309                fprintf(stderr, "emulator: ignoring bad -scale argument '%s': %s\n", opts->scale,
310                        "not a number or the 'auto' keyword" );
311                exit(1);
312            }
313            else if ( scale < 0.1 || scale > 3. ) {
314                fprintf(stderr, "emulator: ignoring bad -window-scale argument '%s': %s\n", opts->scale,
315                        "must be between 0.1 and 3.0" );
316                exit(1);
317            }
318        }
319    }
320
321    if (scale == 0.0 && dpi_monitor > 0)
322        scale = dpi_monitor*1.0/dpi_device;
323
324    return scale;
325}
326
327/* used to respond to a given keyboard command shortcut
328 */
329static void
330handle_key_command( void*  opaque, SkinKeyCommand  command, int  down )
331{
332    static const struct { SkinKeyCommand  cmd; AndroidKeyCode  kcode; }  keycodes[] =
333    {
334        { SKIN_KEY_COMMAND_BUTTON_CALL,        kKeyCodeCall },
335        { SKIN_KEY_COMMAND_BUTTON_HOME,        kKeyCodeHome },
336        { SKIN_KEY_COMMAND_BUTTON_BACK,        kKeyCodeBack },
337        { SKIN_KEY_COMMAND_BUTTON_HANGUP,      kKeyCodeEndCall },
338        { SKIN_KEY_COMMAND_BUTTON_POWER,       kKeyCodePower },
339        { SKIN_KEY_COMMAND_BUTTON_SEARCH,      kKeyCodeSearch },
340        { SKIN_KEY_COMMAND_BUTTON_MENU,        kKeyCodeMenu },
341        { SKIN_KEY_COMMAND_BUTTON_DPAD_UP,     kKeyCodeDpadUp },
342        { SKIN_KEY_COMMAND_BUTTON_DPAD_LEFT,   kKeyCodeDpadLeft },
343        { SKIN_KEY_COMMAND_BUTTON_DPAD_RIGHT,  kKeyCodeDpadRight },
344        { SKIN_KEY_COMMAND_BUTTON_DPAD_DOWN,   kKeyCodeDpadDown },
345        { SKIN_KEY_COMMAND_BUTTON_DPAD_CENTER, kKeyCodeDpadCenter },
346        { SKIN_KEY_COMMAND_BUTTON_VOLUME_UP,   kKeyCodeVolumeUp },
347        { SKIN_KEY_COMMAND_BUTTON_VOLUME_DOWN, kKeyCodeVolumeDown },
348        { SKIN_KEY_COMMAND_BUTTON_CAMERA,      kKeyCodeCamera },
349        { SKIN_KEY_COMMAND_BUTTON_TV,          kKeyCodeTV },
350        { SKIN_KEY_COMMAND_BUTTON_EPG,         kKeyCodeEPG },
351        { SKIN_KEY_COMMAND_BUTTON_DVR,         kKeyCodeDVR },
352        { SKIN_KEY_COMMAND_BUTTON_PREV,        kKeyCodePrevious },
353        { SKIN_KEY_COMMAND_BUTTON_NEXT,        kKeyCodeNext },
354        { SKIN_KEY_COMMAND_BUTTON_PLAY,        kKeyCodePlay },
355        { SKIN_KEY_COMMAND_BUTTON_PAUSE,       kKeyCodePause },
356        { SKIN_KEY_COMMAND_BUTTON_STOP,        kKeyCodeStop },
357        { SKIN_KEY_COMMAND_BUTTON_REWIND,      kKeyCodeRewind },
358        { SKIN_KEY_COMMAND_BUTTON_FFWD,        kKeyCodeFastForward },
359        { SKIN_KEY_COMMAND_BUTTON_BOOKMARKS,   kKeyCodeBookmarks },
360        { SKIN_KEY_COMMAND_BUTTON_WINDOW,      kKeyCodeCycleWindows },
361        { SKIN_KEY_COMMAND_BUTTON_CHANNELUP,   kKeyCodeChannelUp },
362        { SKIN_KEY_COMMAND_BUTTON_CHANNELDOWN, kKeyCodeChannelDown },
363        { SKIN_KEY_COMMAND_NONE, 0 }
364    };
365    int          nn;
366#ifdef CONFIG_TRACE
367    static int   tracing = 0;
368#endif
369    QEmulator*   emulator = opaque;
370
371
372    for (nn = 0; keycodes[nn].kcode != 0; nn++) {
373        if (command == keycodes[nn].cmd) {
374            unsigned  code = keycodes[nn].kcode;
375            if (down)
376                code |= 0x200;
377            user_event_keycode( code );
378            return;
379        }
380    }
381
382    // for the show-trackball command, handle down events to enable, and
383    // up events to disable
384    if (command == SKIN_KEY_COMMAND_SHOW_TRACKBALL) {
385        emulator->show_trackball = (down != 0);
386        skin_window_show_trackball( emulator->window, emulator->show_trackball );
387        //qemulator_set_title( emulator );
388        return;
389    }
390
391    // only handle down events for the rest
392    if (down == 0)
393        return;
394
395    switch (command)
396    {
397    case SKIN_KEY_COMMAND_TOGGLE_NETWORK:
398        {
399            corecmd_toggle_network();
400            D( "network is now %s", corecmd_is_network_disabled() ?
401                                    "disconnected" : "connected" );
402        }
403        break;
404
405    case SKIN_KEY_COMMAND_TOGGLE_FULLSCREEN:
406        if (emulator->window) {
407            skin_window_toggle_fullscreen(emulator->window);
408        }
409        break;
410
411    case SKIN_KEY_COMMAND_TOGGLE_TRACING:
412        {
413#ifdef CONFIG_TRACE
414            tracing = !tracing;
415            corecmd_trace_control(tracing);
416#endif
417        }
418        break;
419
420    case SKIN_KEY_COMMAND_TOGGLE_TRACKBALL:
421        emulator->show_trackball = !emulator->show_trackball;
422        skin_window_show_trackball( emulator->window, emulator->show_trackball );
423        qemulator_set_title(emulator);
424        break;
425
426    case SKIN_KEY_COMMAND_ONION_ALPHA_UP:
427    case SKIN_KEY_COMMAND_ONION_ALPHA_DOWN:
428        if (emulator->onion)
429        {
430            int  alpha = emulator->onion_alpha;
431
432            if (command == SKIN_KEY_COMMAND_ONION_ALPHA_UP)
433                alpha += 16;
434            else
435                alpha -= 16;
436
437            if (alpha > 256)
438                alpha = 256;
439            else if (alpha < 0)
440                alpha = 0;
441
442            emulator->onion_alpha = alpha;
443            skin_window_set_onion( emulator->window, emulator->onion, emulator->onion_rotation, alpha );
444            skin_window_redraw( emulator->window, NULL );
445            //dprint( "onion alpha set to %d (%.f %%)", alpha, alpha/2.56 );
446        }
447        break;
448
449    case SKIN_KEY_COMMAND_CHANGE_LAYOUT_PREV:
450    case SKIN_KEY_COMMAND_CHANGE_LAYOUT_NEXT:
451        {
452            SkinLayout*  layout = NULL;
453
454            if (command == SKIN_KEY_COMMAND_CHANGE_LAYOUT_NEXT) {
455                layout = emulator->layout->next;
456                if (layout == NULL)
457                    layout = emulator->layout_file->layouts;
458            }
459            else if (command == SKIN_KEY_COMMAND_CHANGE_LAYOUT_PREV) {
460                layout = emulator->layout_file->layouts;
461                while (layout->next && layout->next != emulator->layout)
462                    layout = layout->next;
463            }
464            if (layout != NULL) {
465                SkinRotation  rotation;
466
467                emulator->layout = layout;
468                skin_window_reset( emulator->window, layout );
469
470                rotation = skin_layout_get_dpad_rotation( layout );
471
472                if (emulator->keyboard)
473                    skin_keyboard_set_rotation( emulator->keyboard, rotation );
474
475                if (emulator->trackball) {
476                    skin_trackball_set_rotation( emulator->trackball, rotation );
477                    skin_window_set_trackball( emulator->window, emulator->trackball );
478                    skin_window_show_trackball( emulator->window, emulator->show_trackball );
479                }
480
481                skin_window_set_lcd_brightness( emulator->window, emulator->lcd_brightness );
482
483                qframebuffer_invalidate_all();
484                qframebuffer_check_updates();
485            }
486        }
487        break;
488
489    default:
490        /* XXX: TODO ? */
491        ;
492    }
493}
494
495/* called periodically to poll for user input events */
496static void qemulator_refresh(QEmulator* emulator)
497{
498    SDL_Event      ev;
499    SkinWindow*    window   = emulator->window;
500    SkinKeyboard*  keyboard = emulator->keyboard;
501
502   /* this will eventually call sdl_update if the content of the VGA framebuffer
503    * has changed */
504    qframebuffer_check_updates();
505
506    if (window == NULL)
507        return;
508
509    while(SDL_PollEvent(&ev)){
510        switch(ev.type){
511        case SDL_VIDEOEXPOSE:
512            skin_window_redraw( window, NULL );
513            break;
514
515        case SDL_KEYDOWN:
516#ifdef _WIN32
517            /* special code to deal with Alt-F4 properly */
518            if (ev.key.keysym.sym == SDLK_F4 &&
519                ev.key.keysym.mod & KMOD_ALT) {
520              goto CleanExit;
521            }
522#endif
523#ifdef __APPLE__
524            /* special code to deal with Command-Q properly */
525            if (ev.key.keysym.sym == SDLK_q &&
526                ev.key.keysym.mod & KMOD_META) {
527              goto CleanExit;
528            }
529#endif
530            skin_keyboard_process_event( keyboard, &ev, 1 );
531            break;
532
533        case SDL_KEYUP:
534            skin_keyboard_process_event( keyboard, &ev, 0 );
535            break;
536
537        case SDL_MOUSEMOTION:
538            skin_window_process_event( window, &ev );
539            break;
540
541        case SDL_MOUSEBUTTONDOWN:
542        case SDL_MOUSEBUTTONUP:
543            {
544                int  down = (ev.type == SDL_MOUSEBUTTONDOWN);
545                if (ev.button.button == 4)
546                {
547                    /* scroll-wheel simulates DPad up */
548                    AndroidKeyCode  kcode;
549
550                    kcode = // qemulator_rotate_keycode(kKeyCodeDpadUp);
551                        android_keycode_rotate(kKeyCodeDpadUp,
552                            skin_layout_get_dpad_rotation(qemulator_get_layout(qemulator_get())));
553                    user_event_key( kcode, down );
554                }
555                else if (ev.button.button == 5)
556                {
557                    /* scroll-wheel simulates DPad down */
558                    AndroidKeyCode  kcode;
559
560                    kcode = // qemulator_rotate_keycode(kKeyCodeDpadDown);
561                        android_keycode_rotate(kKeyCodeDpadDown,
562                            skin_layout_get_dpad_rotation(qemulator_get_layout(qemulator_get())));
563                    user_event_key( kcode, down );
564                }
565                else if (ev.button.button == SDL_BUTTON_LEFT) {
566                    skin_window_process_event( window, &ev );
567                }
568#if 0
569                else {
570                fprintf(stderr, "... mouse button %s: button=%d state=%04x x=%d y=%d\n",
571                                down ? "down" : "up  ",
572                                ev.button.button, ev.button.state, ev.button.x, ev.button.y);
573                }
574#endif
575            }
576            break;
577
578        case SDL_QUIT:
579#if defined _WIN32 || defined __APPLE__
580        CleanExit:
581#endif
582            /* only save emulator config through clean exit */
583            qemulator_done(qemulator_get());
584            qemu_system_shutdown_request();
585            return;
586        }
587    }
588
589    skin_keyboard_flush( keyboard );
590}
591
592/*
593 * android/console.c helper routines.
594 */
595
596SkinKeyboard*
597android_emulator_get_keyboard(void)
598{
599    return qemulator->keyboard;
600}
601
602void
603android_emulator_set_window_scale( double  scale, int  is_dpi )
604{
605    QEmulator*  emulator = qemulator;
606
607    if (is_dpi)
608        scale /= get_device_dpi( emulator->opts );
609
610    if (emulator->window)
611        skin_window_set_scale( emulator->window, scale );
612}
613
614
615void
616android_emulator_set_base_port( int  port )
617{
618    /* Base port is already set in the emulator's core. */
619    qemulator_set_title(qemulator);
620}
621