/* Copyright (C) 2006-2010 The Android Open Source Project ** ** This software is licensed under the terms of the GNU General Public ** License version 2, as published by the Free Software Foundation, and ** may be copied, distributed, and modified under those terms. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. */ #include "android/utils/debug.h" #include "android/utils/bufprint.h" #include "android/globals.h" #include "android/qemulator.h" #include "android/protocol/core-commands-api.h" #include "android/protocol/ui-commands-api.h" #include "user-events.h" #define D(...) do { if (VERBOSE_CHECK(init)) dprint(__VA_ARGS__); } while (0) static double get_default_scale( AndroidOptions* opts ); /* QEmulator structure instance. */ static QEmulator qemulator[1]; static void handle_key_command( void* opaque, SkinKeyCommand command, int param ); static void qemulator_refresh(QEmulator* emulator); extern void qemu_system_shutdown_request(void); static void qemulator_light_brightness( void* opaque, const char* light, int value ) { QEmulator* emulator = opaque; VERBOSE_PRINT(hw_control,"%s: light='%s' value=%d window=%p", __FUNCTION__, light, value, emulator->window); if ( !strcmp(light, "lcd_backlight") ) { emulator->lcd_brightness = value; if (emulator->window) skin_window_set_lcd_brightness( emulator->window, value ); return; } } static void qemulator_setup( QEmulator* emulator ) { AndroidOptions* opts = emulator->opts; if ( !emulator->window && !opts->no_window ) { SkinLayout* layout = emulator->layout; double scale = get_default_scale(emulator->opts); emulator->window = skin_window_create( layout, emulator->win_x, emulator->win_y, scale, 0); if (emulator->window == NULL) return; { SkinTrackBall* ball; SkinTrackBallParameters params; params.diameter = 30; params.ring = 2; params.ball_color = 0xffe0e0e0; params.dot_color = 0xff202020; params.ring_color = 0xff000000; ball = skin_trackball_create( ¶ms ); emulator->trackball = ball; skin_window_set_trackball( emulator->window, ball ); emulator->lcd_brightness = 128; /* 50% */ skin_window_set_lcd_brightness( emulator->window, emulator->lcd_brightness ); } if ( emulator->onion != NULL ) skin_window_set_onion( emulator->window, emulator->onion, emulator->onion_rotation, emulator->onion_alpha ); qemulator_set_title(emulator); skin_window_enable_touch ( emulator->window, !androidHwConfig_isScreenNoTouch(android_hw)); skin_window_enable_dpad ( emulator->window, android_hw->hw_dPad != 0 ); skin_window_enable_qwerty( emulator->window, android_hw->hw_keyboard != 0 ); skin_window_enable_trackball( emulator->window, android_hw->hw_trackBall != 0 ); } /* initialize hardware control support */ uicmd_set_brightness_change_callback(qemulator_light_brightness, emulator); } static void qemulator_fb_update( void* _emulator, int x, int y, int w, int h ) { QEmulator* emulator = _emulator; if (!emulator->window) { if (emulator->opts->no_window) return; qemulator_setup( emulator ); } skin_window_update_display( emulator->window, x, y, w, h ); } static void qemulator_fb_rotate( void* _emulator, int rotation ) { QEmulator* emulator = _emulator; qemulator_setup( emulator ); } static void qemulator_fb_poll( void* _emulator ) { QEmulator* emulator = _emulator; qemulator_refresh(emulator); } QEmulator* qemulator_get(void) { return qemulator; } int qemulator_init( QEmulator* emulator, AConfig* aconfig, const char* basepath, int x, int y, AndroidOptions* opts ) { emulator->aconfig = aconfig; emulator->layout_file = skin_file_create_from_aconfig(aconfig, basepath); emulator->layout = emulator->layout_file->layouts; emulator->keyboard = skin_keyboard_create(opts->charmap, opts->raw_keys); emulator->window = NULL; emulator->win_x = x; emulator->win_y = y; emulator->opts[0] = opts[0]; /* register as a framebuffer clients for all displays defined in the skin file */ SKIN_FILE_LOOP_PARTS( emulator->layout_file, part ) SkinDisplay* disp = part->display; if (disp->valid) { qframebuffer_add_client( disp->qfbuff, emulator, qemulator_fb_update, qemulator_fb_rotate, qemulator_fb_poll, NULL ); } SKIN_FILE_LOOP_END_PARTS skin_keyboard_enable( emulator->keyboard, 1 ); skin_keyboard_on_command( emulator->keyboard, handle_key_command, emulator ); return 0; } void qemulator_done(QEmulator* emulator) { if (emulator->window) { skin_window_free(emulator->window); emulator->window = NULL; } if (emulator->trackball) { skin_trackball_destroy(emulator->trackball); emulator->trackball = NULL; } if (emulator->keyboard) { skin_keyboard_free(emulator->keyboard); emulator->keyboard = NULL; } emulator->layout = NULL; if (emulator->layout_file) { skin_file_free(emulator->layout_file); emulator->layout_file = NULL; } } SkinLayout* qemulator_get_layout(QEmulator* emulator) { return emulator->layout; } QFrameBuffer* qemulator_get_first_framebuffer(QEmulator* emulator) { /* register as a framebuffer clients for all displays defined in the skin file */ SKIN_FILE_LOOP_PARTS( emulator->layout_file, part ) SkinDisplay* disp = part->display; if (disp->valid) { return disp->qfbuff; } SKIN_FILE_LOOP_END_PARTS return NULL; } void qemulator_set_title(QEmulator* emulator) { char temp[128], *p=temp, *end=p+sizeof temp;; if (emulator->window == NULL) return; if (emulator->show_trackball) { SkinKeyBinding bindings[ SKIN_KEY_COMMAND_MAX_BINDINGS ]; int count; count = skin_keyset_get_bindings( android_keyset, SKIN_KEY_COMMAND_TOGGLE_TRACKBALL, bindings ); if (count > 0) { int nn; p = bufprint( p, end, "Press " ); for (nn = 0; nn < count; nn++) { if (nn > 0) { if (nn < count-1) p = bufprint(p, end, ", "); else p = bufprint(p, end, " or "); } p = bufprint(p, end, "%s", skin_key_symmod_to_str( bindings[nn].sym, bindings[nn].mod ) ); } p = bufprint(p, end, " to leave trackball mode. "); } } p = bufprint(p, end, "%d:%s", android_base_port, avdInfo_getName( android_avdInfo )); skin_window_set_title( emulator->window, temp ); } /* * Helper routines */ static int get_device_dpi( AndroidOptions* opts ) { int dpi_device = corecmd_get_hw_lcd_density(); if (opts->dpi_device != NULL) { char* end; dpi_device = strtol( opts->dpi_device, &end, 0 ); if (end == NULL || *end != 0 || dpi_device <= 0) { fprintf(stderr, "argument for -dpi-device must be a positive integer. Aborting\n" ); exit(1); } } return dpi_device; } static double get_default_scale( AndroidOptions* opts ) { int dpi_device = get_device_dpi( opts ); int dpi_monitor = -1; double scale = 0.0; /* possible values for the 'scale' option are * 'auto' : try to determine the scale automatically * 'dpi' : indicates the host monitor dpi, compute scale accordingly * '' : use direct scale coefficient */ if (opts->scale) { if (!strcmp(opts->scale, "auto")) { /* we need to get the host dpi resolution ? */ int xdpi, ydpi; if ( SDL_WM_GetMonitorDPI( &xdpi, &ydpi ) < 0 ) { fprintf(stderr, "could not get monitor DPI resolution from system. please use -dpi-monitor to specify one\n" ); exit(1); } D( "system reported monitor resolutions: xdpi=%d ydpi=%d\n", xdpi, ydpi); dpi_monitor = (xdpi + ydpi+1)/2; } else { char* end; scale = strtod( opts->scale, &end ); if (end && end[0] == 'd' && end[1] == 'p' && end[2] == 'i' && end[3] == 0) { if ( scale < 20 || scale > 1000 ) { fprintf(stderr, "emulator: ignoring bad -scale argument '%s': %s\n", opts->scale, "host dpi number must be between 20 and 1000" ); exit(1); } dpi_monitor = scale; scale = 0.0; } else if (end == NULL || *end != 0) { fprintf(stderr, "emulator: ignoring bad -scale argument '%s': %s\n", opts->scale, "not a number or the 'auto' keyword" ); exit(1); } else if ( scale < 0.1 || scale > 3. ) { fprintf(stderr, "emulator: ignoring bad -window-scale argument '%s': %s\n", opts->scale, "must be between 0.1 and 3.0" ); exit(1); } } } if (scale == 0.0 && dpi_monitor > 0) scale = dpi_monitor*1.0/dpi_device; return scale; } /* used to respond to a given keyboard command shortcut */ static void handle_key_command( void* opaque, SkinKeyCommand command, int down ) { static const struct { SkinKeyCommand cmd; AndroidKeyCode kcode; } keycodes[] = { { SKIN_KEY_COMMAND_BUTTON_CALL, kKeyCodeCall }, { SKIN_KEY_COMMAND_BUTTON_HOME, kKeyCodeHome }, { SKIN_KEY_COMMAND_BUTTON_BACK, kKeyCodeBack }, { SKIN_KEY_COMMAND_BUTTON_HANGUP, kKeyCodeEndCall }, { SKIN_KEY_COMMAND_BUTTON_POWER, kKeyCodePower }, { SKIN_KEY_COMMAND_BUTTON_SEARCH, kKeyCodeSearch }, { SKIN_KEY_COMMAND_BUTTON_MENU, kKeyCodeMenu }, { SKIN_KEY_COMMAND_BUTTON_DPAD_UP, kKeyCodeDpadUp }, { SKIN_KEY_COMMAND_BUTTON_DPAD_LEFT, kKeyCodeDpadLeft }, { SKIN_KEY_COMMAND_BUTTON_DPAD_RIGHT, kKeyCodeDpadRight }, { SKIN_KEY_COMMAND_BUTTON_DPAD_DOWN, kKeyCodeDpadDown }, { SKIN_KEY_COMMAND_BUTTON_DPAD_CENTER, kKeyCodeDpadCenter }, { SKIN_KEY_COMMAND_BUTTON_VOLUME_UP, kKeyCodeVolumeUp }, { SKIN_KEY_COMMAND_BUTTON_VOLUME_DOWN, kKeyCodeVolumeDown }, { SKIN_KEY_COMMAND_BUTTON_CAMERA, kKeyCodeCamera }, { SKIN_KEY_COMMAND_BUTTON_TV, kKeyCodeTV }, { SKIN_KEY_COMMAND_BUTTON_EPG, kKeyCodeEPG }, { SKIN_KEY_COMMAND_BUTTON_DVR, kKeyCodeDVR }, { SKIN_KEY_COMMAND_BUTTON_PREV, kKeyCodePrevious }, { SKIN_KEY_COMMAND_BUTTON_NEXT, kKeyCodeNext }, { SKIN_KEY_COMMAND_BUTTON_PLAY, kKeyCodePlay }, { SKIN_KEY_COMMAND_BUTTON_PAUSE, kKeyCodePause }, { SKIN_KEY_COMMAND_BUTTON_STOP, kKeyCodeStop }, { SKIN_KEY_COMMAND_BUTTON_REWIND, kKeyCodeRewind }, { SKIN_KEY_COMMAND_BUTTON_FFWD, kKeyCodeFastForward }, { SKIN_KEY_COMMAND_BUTTON_BOOKMARKS, kKeyCodeBookmarks }, { SKIN_KEY_COMMAND_BUTTON_WINDOW, kKeyCodeCycleWindows }, { SKIN_KEY_COMMAND_BUTTON_CHANNELUP, kKeyCodeChannelUp }, { SKIN_KEY_COMMAND_BUTTON_CHANNELDOWN, kKeyCodeChannelDown }, { SKIN_KEY_COMMAND_NONE, 0 } }; int nn; #ifdef CONFIG_TRACE static int tracing = 0; #endif QEmulator* emulator = opaque; for (nn = 0; keycodes[nn].kcode != 0; nn++) { if (command == keycodes[nn].cmd) { unsigned code = keycodes[nn].kcode; if (down) code |= 0x200; user_event_keycode( code ); return; } } // for the show-trackball command, handle down events to enable, and // up events to disable if (command == SKIN_KEY_COMMAND_SHOW_TRACKBALL) { emulator->show_trackball = (down != 0); skin_window_show_trackball( emulator->window, emulator->show_trackball ); //qemulator_set_title( emulator ); return; } // only handle down events for the rest if (down == 0) return; switch (command) { case SKIN_KEY_COMMAND_TOGGLE_NETWORK: { corecmd_toggle_network(); D( "network is now %s", corecmd_is_network_disabled() ? "disconnected" : "connected" ); } break; case SKIN_KEY_COMMAND_TOGGLE_FULLSCREEN: if (emulator->window) { skin_window_toggle_fullscreen(emulator->window); } break; case SKIN_KEY_COMMAND_TOGGLE_TRACING: { #ifdef CONFIG_TRACE tracing = !tracing; corecmd_trace_control(tracing); #endif } break; case SKIN_KEY_COMMAND_TOGGLE_TRACKBALL: emulator->show_trackball = !emulator->show_trackball; skin_window_show_trackball( emulator->window, emulator->show_trackball ); qemulator_set_title(emulator); break; case SKIN_KEY_COMMAND_ONION_ALPHA_UP: case SKIN_KEY_COMMAND_ONION_ALPHA_DOWN: if (emulator->onion) { int alpha = emulator->onion_alpha; if (command == SKIN_KEY_COMMAND_ONION_ALPHA_UP) alpha += 16; else alpha -= 16; if (alpha > 256) alpha = 256; else if (alpha < 0) alpha = 0; emulator->onion_alpha = alpha; skin_window_set_onion( emulator->window, emulator->onion, emulator->onion_rotation, alpha ); skin_window_redraw( emulator->window, NULL ); //dprint( "onion alpha set to %d (%.f %%)", alpha, alpha/2.56 ); } break; case SKIN_KEY_COMMAND_CHANGE_LAYOUT_PREV: case SKIN_KEY_COMMAND_CHANGE_LAYOUT_NEXT: { SkinLayout* layout = NULL; if (command == SKIN_KEY_COMMAND_CHANGE_LAYOUT_NEXT) { layout = emulator->layout->next; if (layout == NULL) layout = emulator->layout_file->layouts; } else if (command == SKIN_KEY_COMMAND_CHANGE_LAYOUT_PREV) { layout = emulator->layout_file->layouts; while (layout->next && layout->next != emulator->layout) layout = layout->next; } if (layout != NULL) { SkinRotation rotation; emulator->layout = layout; skin_window_reset( emulator->window, layout ); rotation = skin_layout_get_dpad_rotation( layout ); if (emulator->keyboard) skin_keyboard_set_rotation( emulator->keyboard, rotation ); if (emulator->trackball) { skin_trackball_set_rotation( emulator->trackball, rotation ); skin_window_set_trackball( emulator->window, emulator->trackball ); skin_window_show_trackball( emulator->window, emulator->show_trackball ); } skin_window_set_lcd_brightness( emulator->window, emulator->lcd_brightness ); qframebuffer_invalidate_all(); qframebuffer_check_updates(); } } break; default: /* XXX: TODO ? */ ; } } /* called periodically to poll for user input events */ static void qemulator_refresh(QEmulator* emulator) { SDL_Event ev; SkinWindow* window = emulator->window; SkinKeyboard* keyboard = emulator->keyboard; /* this will eventually call sdl_update if the content of the VGA framebuffer * has changed */ qframebuffer_check_updates(); if (window == NULL) return; while(SDL_PollEvent(&ev)){ switch(ev.type){ case SDL_VIDEOEXPOSE: skin_window_redraw( window, NULL ); break; case SDL_KEYDOWN: #ifdef _WIN32 /* special code to deal with Alt-F4 properly */ if (ev.key.keysym.sym == SDLK_F4 && ev.key.keysym.mod & KMOD_ALT) { goto CleanExit; } #endif #ifdef __APPLE__ /* special code to deal with Command-Q properly */ if (ev.key.keysym.sym == SDLK_q && ev.key.keysym.mod & KMOD_META) { goto CleanExit; } #endif skin_keyboard_process_event( keyboard, &ev, 1 ); break; case SDL_KEYUP: skin_keyboard_process_event( keyboard, &ev, 0 ); break; case SDL_MOUSEMOTION: skin_window_process_event( window, &ev ); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { int down = (ev.type == SDL_MOUSEBUTTONDOWN); if (ev.button.button == 4) { /* scroll-wheel simulates DPad up */ AndroidKeyCode kcode; kcode = // qemulator_rotate_keycode(kKeyCodeDpadUp); android_keycode_rotate(kKeyCodeDpadUp, skin_layout_get_dpad_rotation(qemulator_get_layout(qemulator_get()))); user_event_key( kcode, down ); } else if (ev.button.button == 5) { /* scroll-wheel simulates DPad down */ AndroidKeyCode kcode; kcode = // qemulator_rotate_keycode(kKeyCodeDpadDown); android_keycode_rotate(kKeyCodeDpadDown, skin_layout_get_dpad_rotation(qemulator_get_layout(qemulator_get()))); user_event_key( kcode, down ); } else if (ev.button.button == SDL_BUTTON_LEFT) { skin_window_process_event( window, &ev ); } #if 0 else { fprintf(stderr, "... mouse button %s: button=%d state=%04x x=%d y=%d\n", down ? "down" : "up ", ev.button.button, ev.button.state, ev.button.x, ev.button.y); } #endif } break; case SDL_QUIT: #if defined _WIN32 || defined __APPLE__ CleanExit: #endif /* only save emulator config through clean exit */ qemulator_done(qemulator_get()); qemu_system_shutdown_request(); return; } } skin_keyboard_flush( keyboard ); } /* * android/console.c helper routines. */ SkinKeyboard* android_emulator_get_keyboard(void) { return qemulator->keyboard; } void android_emulator_set_window_scale( double scale, int is_dpi ) { QEmulator* emulator = qemulator; if (is_dpi) scale /= get_device_dpi( emulator->opts ); if (emulator->window) skin_window_set_scale( emulator->window, scale ); } void android_emulator_set_base_port( int port ) { /* Base port is already set in the emulator's core. */ qemulator_set_title(qemulator); }