You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
410 lines
11 KiB
410 lines
11 KiB
/*
|
|
* This file is part of the Trezor project, https://trezor.io/
|
|
*
|
|
* Copyright (c) SatoshiLabs
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <xdisplay.h>
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_image.h>
|
|
|
|
#include "common.h"
|
|
#include "profile.h"
|
|
|
|
#define EMULATOR_BORDER 16
|
|
|
|
#if defined TREZOR_MODEL_T
|
|
|
|
#ifdef TREZOR_EMULATOR_RASPI
|
|
#define WINDOW_WIDTH 480
|
|
#define WINDOW_HEIGHT 320
|
|
#define TOUCH_OFFSET_X 110
|
|
#define TOUCH_OFFSET_Y 40
|
|
#else
|
|
#define WINDOW_WIDTH 400
|
|
#define WINDOW_HEIGHT 600
|
|
#define TOUCH_OFFSET_X 80
|
|
#define TOUCH_OFFSET_Y 110
|
|
#endif
|
|
|
|
#elif defined TREZOR_MODEL_1
|
|
|
|
#define WINDOW_WIDTH 200
|
|
#define WINDOW_HEIGHT 340
|
|
#define TOUCH_OFFSET_X 36
|
|
#define TOUCH_OFFSET_Y 92
|
|
|
|
#elif defined TREZOR_MODEL_R
|
|
|
|
#define WINDOW_WIDTH 193
|
|
#define WINDOW_HEIGHT 339
|
|
#define TOUCH_OFFSET_X 32
|
|
#define TOUCH_OFFSET_Y 84
|
|
|
|
#elif defined TREZOR_MODEL_T3T1
|
|
|
|
#define WINDOW_WIDTH 400
|
|
#define WINDOW_HEIGHT 600
|
|
#define TOUCH_OFFSET_X 80
|
|
#define TOUCH_OFFSET_Y 110
|
|
|
|
#else
|
|
#error Unknown Trezor model
|
|
#endif
|
|
|
|
typedef struct {
|
|
// Current display orientation (0 or 180)
|
|
int orientation_angle;
|
|
// Current backlight level ranging from 0 to 255
|
|
int backlight_level;
|
|
|
|
SDL_Window *window;
|
|
SDL_Renderer *renderer;
|
|
SDL_Surface *buffer;
|
|
SDL_Texture *texture;
|
|
SDL_Texture *background;
|
|
SDL_Surface *prev_saved;
|
|
|
|
#if DISPLAY_MONO
|
|
// SDL2 does not support 8bit surface/texture
|
|
// and we have to simulate it
|
|
uint8_t mono_framebuf[DISPLAY_RESX * DISPLAY_RESY];
|
|
#endif
|
|
|
|
} display_driver_t;
|
|
|
|
static display_driver_t g_display_driver;
|
|
|
|
//!@# TODO get rid of this...
|
|
int sdl_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY;
|
|
int sdl_touch_offset_x, sdl_touch_offset_y;
|
|
|
|
void display_deinit(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
SDL_FreeSurface(drv->prev_saved);
|
|
SDL_FreeSurface(drv->buffer);
|
|
if (drv->background != NULL) {
|
|
SDL_DestroyTexture(drv->background);
|
|
}
|
|
if (drv->texture != NULL) {
|
|
SDL_DestroyTexture(drv->texture);
|
|
}
|
|
if (drv->renderer != NULL) {
|
|
SDL_DestroyRenderer(drv->renderer);
|
|
}
|
|
if (drv->window != NULL) {
|
|
SDL_DestroyWindow(drv->window);
|
|
}
|
|
SDL_Quit();
|
|
}
|
|
|
|
void display_init(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
printf("%s\n", SDL_GetError());
|
|
ensure(secfalse, "SDL_Init error");
|
|
}
|
|
atexit(display_deinit);
|
|
|
|
char *window_title = NULL;
|
|
char *window_title_alloc = NULL;
|
|
if (asprintf(&window_title_alloc, "Trezor^emu: %s", profile_name()) > 0) {
|
|
window_title = window_title_alloc;
|
|
} else {
|
|
window_title = "Trezor^emu";
|
|
window_title_alloc = NULL;
|
|
}
|
|
|
|
drv->window =
|
|
SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT,
|
|
#ifdef TREZOR_EMULATOR_RASPI
|
|
SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN
|
|
#else
|
|
SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI
|
|
#endif
|
|
);
|
|
free(window_title_alloc);
|
|
if (!drv->window) {
|
|
printf("%s\n", SDL_GetError());
|
|
ensure(secfalse, "SDL_CreateWindow error");
|
|
}
|
|
drv->renderer = SDL_CreateRenderer(drv->window, -1, SDL_RENDERER_SOFTWARE);
|
|
if (!drv->renderer) {
|
|
printf("%s\n", SDL_GetError());
|
|
SDL_DestroyWindow(drv->window);
|
|
ensure(secfalse, "SDL_CreateRenderer error");
|
|
}
|
|
SDL_SetRenderDrawColor(drv->renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(drv->renderer);
|
|
|
|
drv->buffer = SDL_CreateRGBSurface(0, DISPLAY_RESX, DISPLAY_RESY, 16, 0xF800,
|
|
0x07E0, 0x001F, 0x0000);
|
|
drv->texture = SDL_CreateTexture(drv->renderer, SDL_PIXELFORMAT_RGB565,
|
|
SDL_TEXTUREACCESS_STREAMING, DISPLAY_RESX,
|
|
DISPLAY_RESY);
|
|
SDL_SetTextureBlendMode(drv->texture, SDL_BLENDMODE_BLEND);
|
|
#ifdef __APPLE__
|
|
// macOS Mojave SDL black screen workaround
|
|
SDL_PumpEvents();
|
|
SDL_SetWindowSize(WINDOW, WINDOW_WIDTH, WINDOW_HEIGHT);
|
|
#endif
|
|
#ifdef TREZOR_EMULATOR_RASPI
|
|
#include "background_raspi.h"
|
|
drv->background = IMG_LoadTexture_RW(
|
|
drv->renderer,
|
|
SDL_RWFromMem(background_raspi_jpg, background_raspi_jpg_len), 0);
|
|
#else
|
|
#if defined TREZOR_MODEL_T
|
|
#include "background_T.h"
|
|
drv->background = IMG_LoadTexture_RW(
|
|
drv->renderer, SDL_RWFromMem(background_T_jpg, background_T_jpg_len), 0);
|
|
#elif defined TREZOR_MODEL_1
|
|
#include "background_1.h"
|
|
drv->background = IMG_LoadTexture_RW(
|
|
drv->renderer, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0);
|
|
#elif defined TREZOR_MODEL_R
|
|
#include "background_T2B1.h"
|
|
drv->background = IMG_LoadTexture_RW(
|
|
drv->renderer,
|
|
SDL_RWFromMem(background_T2B1_png, background_T2B1_png_len), 0);
|
|
#endif
|
|
#endif
|
|
if (drv->background) {
|
|
SDL_SetTextureBlendMode(drv->background, SDL_BLENDMODE_NONE);
|
|
sdl_touch_offset_x = TOUCH_OFFSET_X;
|
|
sdl_touch_offset_y = TOUCH_OFFSET_Y;
|
|
} else {
|
|
SDL_SetWindowSize(drv->window, DISPLAY_RESX + 2 * EMULATOR_BORDER,
|
|
DISPLAY_RESY + 2 * EMULATOR_BORDER);
|
|
sdl_touch_offset_x = EMULATOR_BORDER;
|
|
sdl_touch_offset_y = EMULATOR_BORDER;
|
|
}
|
|
#if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
|
|
// T1 and TR do not have backlight capabilities in hardware, so
|
|
// setting its value here for emulator to avoid
|
|
// calling any `set_backlight` functions
|
|
drv->backlight_level = 255;
|
|
#else
|
|
drv->backlight_level = 0;
|
|
#endif
|
|
#ifdef TREZOR_EMULATOR_RASPI
|
|
drv->orientation_angle = 270;
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
#else
|
|
drv->orientation_angle = 0;
|
|
#endif
|
|
}
|
|
|
|
void display_reinit(void) {
|
|
// not used
|
|
}
|
|
|
|
void display_finish_actions(void) {
|
|
// not used
|
|
}
|
|
|
|
int display_set_backlight(int level) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
#if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
|
|
level = 255;
|
|
#endif
|
|
|
|
if (drv->backlight_level != level && level >= 0 && level <= 255) {
|
|
drv->backlight_level = level;
|
|
display_refresh();
|
|
}
|
|
|
|
return drv->backlight_level;
|
|
}
|
|
|
|
int display_get_backlight(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
return drv->backlight_level;
|
|
}
|
|
|
|
int display_set_orientation(int angle) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
if (angle != drv->orientation_angle) {
|
|
#if defined TREZOR_MODEL_T || defined TREZOR_MODEL_T3T1
|
|
if (angle == 0 || angle == 90 || angle == 180 || angle == 270) {
|
|
#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
|
|
if (angle == 0 || angle == 180) {
|
|
#else
|
|
#error Unknown Trezor model
|
|
#endif
|
|
drv->orientation_angle = angle;
|
|
display_refresh();
|
|
}
|
|
}
|
|
return drv->orientation_angle;
|
|
}
|
|
|
|
int display_get_orientation(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
return drv->orientation_angle;
|
|
}
|
|
|
|
#ifdef XFRAMEBUFFER
|
|
void *display_get_frame_addr(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
#ifdef DISPLAY_MONO
|
|
return drv->mono_framebuf;
|
|
#else
|
|
// !@# pitch???
|
|
return drv->buffer->pixels;
|
|
#endif
|
|
}
|
|
|
|
#else // XFRAMEBUFFER
|
|
|
|
void display_wait_for_sync(void) {
|
|
// not used
|
|
}
|
|
#endif
|
|
|
|
#ifdef DISPLAY_MONO
|
|
// Copies driver's monochromatic framebuffer into the RGB framebuffer used by
|
|
// SDL
|
|
void copy_mono_framebuf(display_driver_t *drv) {
|
|
for (int y = 0; y < DISPLAY_RESY; y++) {
|
|
uint16_t *dst =
|
|
(uint16_t *)((uint8_t *)drv->buffer->pixels + drv->buffer->pitch * y);
|
|
uint8_t *src = &drv->mono_framebuf[y * DISPLAY_RESX];
|
|
for (int x = 0; x < DISPLAY_RESX; x++) {
|
|
uint8_t lum = src[x];
|
|
dst[x] = gl_color16_rgb(lum, lum, lum);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void display_refresh(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
if (!drv->renderer) {
|
|
display_init();
|
|
}
|
|
|
|
#ifdef DISPLAY_MONO
|
|
copy_mono_framebuf(drv);
|
|
#endif
|
|
|
|
if (drv->background) {
|
|
const SDL_Rect r = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
|
|
SDL_RenderCopy(drv->renderer, drv->background, NULL, &r);
|
|
} else {
|
|
SDL_RenderClear(drv->renderer);
|
|
}
|
|
// Show the display buffer
|
|
SDL_UpdateTexture(drv->texture, NULL, drv->buffer->pixels,
|
|
drv->buffer->pitch);
|
|
#define BACKLIGHT_NORMAL 150
|
|
SDL_SetTextureAlphaMod(
|
|
drv->texture, MIN(255, 255 * drv->backlight_level / BACKLIGHT_NORMAL));
|
|
if (drv->background) {
|
|
const SDL_Rect r = {TOUCH_OFFSET_X, TOUCH_OFFSET_Y, DISPLAY_RESX,
|
|
DISPLAY_RESY};
|
|
SDL_RenderCopyEx(drv->renderer, drv->texture, NULL, &r,
|
|
drv->orientation_angle, NULL, 0);
|
|
} else {
|
|
const SDL_Rect r = {EMULATOR_BORDER, EMULATOR_BORDER, DISPLAY_RESX,
|
|
DISPLAY_RESY};
|
|
SDL_RenderCopyEx(drv->renderer, drv->texture, NULL, &r,
|
|
drv->orientation_angle, NULL, 0);
|
|
}
|
|
SDL_RenderPresent(drv->renderer);
|
|
}
|
|
|
|
void display_set_compatible_settings(void) {
|
|
// not used
|
|
}
|
|
|
|
void display_fill(const dma2d_params_t *dp) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
dma2d_params_t dp_new = *dp;
|
|
dp_new.dst_row =
|
|
(uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * dp_new.dst_y);
|
|
dp_new.dst_stride = drv->buffer->pitch;
|
|
|
|
rgb565_fill(&dp_new);
|
|
}
|
|
|
|
void display_copy_rgb565(const dma2d_params_t *dp) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
dma2d_params_t dp_new = *dp;
|
|
dp_new.dst_row =
|
|
(uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * dp_new.dst_y);
|
|
dp_new.dst_stride = drv->buffer->pitch;
|
|
|
|
rgb565_copy_rgb565(&dp_new);
|
|
}
|
|
|
|
void display_copy_mono4(const dma2d_params_t *dp) {
|
|
// !@# TODO
|
|
}
|
|
|
|
void display_copy_mono1p(const dma2d_params_t *dp) {
|
|
// !@# TODO
|
|
}
|
|
|
|
const char *display_save(const char *prefix) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
if (!drv->renderer) {
|
|
display_init();
|
|
}
|
|
static int count;
|
|
static char filename[256];
|
|
// take a cropped view of the screen contents
|
|
const SDL_Rect rect = {0, 0, DISPLAY_RESX, DISPLAY_RESY};
|
|
SDL_Surface *crop = SDL_CreateRGBSurface(
|
|
drv->buffer->flags, rect.w, rect.h, drv->buffer->format->BitsPerPixel,
|
|
drv->buffer->format->Rmask, drv->buffer->format->Gmask,
|
|
drv->buffer->format->Bmask, drv->buffer->format->Amask);
|
|
SDL_BlitSurface(drv->buffer, &rect, crop, NULL);
|
|
// compare with previous screen, skip if equal
|
|
if (drv->prev_saved != NULL) {
|
|
if (memcmp(drv->prev_saved->pixels, crop->pixels, crop->pitch * crop->h) ==
|
|
0) {
|
|
SDL_FreeSurface(crop);
|
|
return filename;
|
|
}
|
|
SDL_FreeSurface(drv->prev_saved);
|
|
}
|
|
// save to png
|
|
snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++);
|
|
IMG_SavePNG(crop, filename);
|
|
drv->prev_saved = crop;
|
|
return filename;
|
|
}
|
|
|
|
void display_clear_save(void) {
|
|
display_driver_t *drv = &g_display_driver;
|
|
|
|
SDL_FreeSurface(drv->prev_saved);
|
|
drv->prev_saved = NULL;
|
|
}
|