aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Marshall <tdm@cyngn.com>2014-11-24 15:11:41 -0800
committerTom Marshall <tdm@cyngn.com>2015-11-25 15:35:20 -0800
commit89e2dcf83fa3901090893e98c17708e279dea47f (patch)
tree23a69ea49d0897fc5052ef44f75afc9a7f53c89a
parentacc855f711ae7bce20e4fc896c829e135a6801fc (diff)
downloadbootable_recovery-89e2dcf83fa3901090893e98c17708e279dea47f.zip
bootable_recovery-89e2dcf83fa3901090893e98c17708e279dea47f.tar.gz
bootable_recovery-89e2dcf83fa3901090893e98c17708e279dea47f.tar.bz2
recovery: Initial dialog implementation
Implement two types of dialogs: info and error. Info dialogs are intended to show that an operation is in progress and cannot be interrupted. Error dialogs are intended to show that an error occurred. The user must respond by dismissing the dialog with any input (a swipe or keypress). Dialogs may be initiated internally within the UI code or externally via a named local socket. Dialogs created via socket are presumed to be info and may not be dismissed. When the client socket is closed, the dialog automatically dismisses. Initial implementation shows dialogs for adb backup and restore. Future work will show dialogs for all errors and lengthy operations. Change-Id: Icefea12ec0fd70fb487d54aa2eb1cae9dd451355
-rw-r--r--Android.mk5
-rw-r--r--backup.cpp8
-rw-r--r--messagesocket.cpp116
-rw-r--r--messagesocket.h40
-rw-r--r--minui/events.cpp17
-rw-r--r--minui/minui.h1
-rw-r--r--res-hdpi/images/icon_error.pngbin19306 -> 4011 bytes
-rw-r--r--res/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--restore.cpp8
-rw-r--r--screen_ui.cpp80
-rw-r--r--screen_ui.h16
-rw-r--r--ui.cpp85
-rw-r--r--ui.h11
-rw-r--r--verifier_test.cpp6
-rw-r--r--wear_ui.cpp2
-rw-r--r--wear_ui.h2
16 files changed, 389 insertions, 8 deletions
diff --git a/Android.mk b/Android.mk
index 55adbaa..f7fbfd8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -41,6 +41,7 @@ LOCAL_SRC_FILES := \
recovery.cpp \
roots.cpp \
screen_ui.cpp \
+ messagesocket.cpp \
ui.cpp \
verifier.cpp \
wear_ui.cpp \
@@ -181,6 +182,7 @@ LOCAL_SRC_FILES := \
bu.cpp \
backup.cpp \
restore.cpp \
+ messagesocket.cpp \
roots.cpp \
voldclient.cpp
LOCAL_CFLAGS += -DMINIVOLD
@@ -283,7 +285,8 @@ LOCAL_SRC_FILES := \
verifier_test.cpp \
asn1_decoder.cpp \
verifier.cpp \
- ui.cpp
+ ui.cpp \
+ messagesocket.cpp
LOCAL_STATIC_LIBRARIES := \
libmincrypt \
libminui \
diff --git a/backup.cpp b/backup.cpp
index ad23b17..5849490 100644
--- a/backup.cpp
+++ b/backup.cpp
@@ -18,6 +18,8 @@
#include "voldclient.h"
+#include "messagesocket.h"
+
using namespace android;
static int append_sod(const char* opt_hash)
@@ -248,6 +250,10 @@ int do_backup(int argc, char **argv)
}
}
+ MessageSocket ms;
+ ms.ClientInit();
+ ms.Show("Backup in progress...");
+
rc = create_tar(adb_ofd, opt_compress, "w");
if (rc != 0) {
logmsg("do_backup: cannot open tar stream\n");
@@ -293,6 +299,8 @@ int do_backup(int argc, char **argv)
if (opt_compress)
gzflush(gzf, Z_FINISH);
+ ms.Dismiss();
+
logmsg("backup complete: rc=%d\n", rc);
return rc;
diff --git a/messagesocket.cpp b/messagesocket.cpp
new file mode 100644
index 0000000..355a667
--- /dev/null
+++ b/messagesocket.cpp
@@ -0,0 +1,116 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include "messagesocket.h"
+
+static const char * const SOCKET_PATH = "/tmp/.dialog_sock";
+
+bool MessageSocket::ServerInit()
+{
+ int fd, rc;
+ unlink(SOCKET_PATH);
+ fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ return false;
+ }
+ struct sockaddr_un sa;
+ socklen_t salen;
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, SOCKET_PATH);
+ rc = ::bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+ if (rc != 0) {
+ ::close(fd);
+ return false;
+ }
+ rc = ::listen(fd, 5);
+ if (rc != 0) {
+ ::close(fd);
+ return false;
+ }
+ _sock = fd;
+ return true;
+}
+
+MessageSocket* MessageSocket::Accept()
+{
+ int clientfd;
+ struct sockaddr_un sa;
+ socklen_t salen;
+ memset(&sa, 0, sizeof(sa));
+ salen = sizeof(sa);
+ clientfd = ::accept(_sock, (struct sockaddr*)&sa, &salen);
+ if (clientfd < 0) {
+ return NULL;
+ }
+ return new MessageSocket(clientfd);
+}
+
+ssize_t MessageSocket::Read(void* buf, size_t len)
+{
+ //XXX: use fdopen/getline
+ ssize_t nread = ::read(_sock, buf, len);
+ if (nread > 0) {
+ char* p = (char*)memchr(buf, '\n', nread);
+ if (p) {
+ *p = '\0';
+ }
+ }
+ return nread;
+}
+
+bool MessageSocket::ClientInit()
+{
+ int fd, rc;
+ fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ return false;
+ }
+ struct sockaddr_un sa;
+ socklen_t salen;
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, SOCKET_PATH);
+ rc = ::connect(fd, (struct sockaddr*)&sa, sizeof(sa));
+ if (rc != 0) {
+ ::close(fd);
+ return false;
+ }
+ _sock = fd;
+ return true;
+}
+
+bool MessageSocket::Show(const char* message)
+{
+ char buf[256];
+ sprintf(buf, "show %s", message);
+ return send_command(buf);
+}
+
+bool MessageSocket::Dismiss()
+{
+ return send_command("dismiss");
+}
+
+void MessageSocket::Close()
+{
+ if (_sock != -1) {
+ ::close(_sock);
+ _sock = -1;
+ }
+}
+
+bool MessageSocket::send_command(const char* command)
+{
+ char buf[256];
+ int len;
+ ssize_t written;
+ len = sprintf(buf, "dialog %s\n", command);
+ written = ::write(_sock, buf, len);
+ return (written == len);
+}
diff --git a/messagesocket.h b/messagesocket.h
new file mode 100644
index 0000000..5a4c67d
--- /dev/null
+++ b/messagesocket.h
@@ -0,0 +1,40 @@
+#ifndef MESSAGESOCKET_H
+#define MESSAGESOCKET_H
+
+class MessageSocket
+{
+private:
+ // Unimplemented
+ MessageSocket(const MessageSocket&);
+ MessageSocket& operator=(const MessageSocket&);
+
+public:
+ MessageSocket() : _sock(-1) {}
+ ~MessageSocket() { Close(); }
+ int fd() const { return _sock; }
+
+ bool ServerInit();
+ MessageSocket* Accept();
+ ssize_t Read(void* buf, size_t len);
+
+ bool ClientInit();
+ bool Show(const char* message);
+ bool Dismiss();
+
+ void Close();
+
+private:
+ explicit MessageSocket(int fd) : _sock(fd) {}
+
+ bool send_command(const char* command);
+
+ int _sock;
+};
+
+extern int dialog_server_init();
+extern int dialog_client_init();
+extern int dialog_accept(int fd);
+extern int dialog_show(int fd);
+extern int dialog_dismiss(int fd);
+
+#endif
diff --git a/minui/events.cpp b/minui/events.cpp
index 3b2262a..4e9d80b 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -137,6 +137,23 @@ int ev_add_fd(int fd, ev_callback cb, void* data) {
return ret;
}
+int ev_del_fd(int fd)
+{
+ unsigned n;
+ for (n = 0; n < ev_count; ++n) {
+ if (ev_fdinfo[n].fd == fd) {
+ epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+ if (n != ev_count-1) {
+ ev_fdinfo[n] = ev_fdinfo[ev_count-1];
+ }
+ ev_count--;
+ ev_misc_count--;
+ return 0;
+ }
+ }
+ return -1;
+}
+
void ev_exit(void) {
while (ev_count > 0) {
close(ev_fdinfo[--ev_count].fd);
diff --git a/minui/minui.h b/minui/minui.h
index d714632..7dec2d7 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -67,6 +67,7 @@ typedef int (*ev_set_key_callback)(int code, int value, void* data);
int ev_init(ev_callback input_cb, void* data);
void ev_exit();
int ev_add_fd(int fd, ev_callback cb, void* data);
+int ev_del_fd(int fd);
void ev_iterate_available_keys(std::function<void(int)> f);
int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data);
diff --git a/res-hdpi/images/icon_error.png b/res-hdpi/images/icon_error.png
index cb3d1ab..a803b24 100644
--- a/res-hdpi/images/icon_error.png
+++ b/res-hdpi/images/icon_error.png
Binary files differ
diff --git a/res/images/icon_info.png b/res/images/icon_info.png
new file mode 100644
index 0000000..3993941
--- /dev/null
+++ b/res/images/icon_info.png
Binary files differ
diff --git a/restore.cpp b/restore.cpp
index 8c15f6f..edfeff8 100644
--- a/restore.cpp
+++ b/restore.cpp
@@ -19,6 +19,8 @@
#include "bu.h"
+#include "messagesocket.h"
+
using namespace android;
static int verify_sod()
@@ -294,9 +296,15 @@ int do_restore(int argc, char **argv)
int len;
int written;
+ MessageSocket ms;
+ ms.ClientInit();
+ ms.Show("Restore in progress...");
+
rc = do_restore_tree();
logmsg("do_restore: rc=%d\n", rc);
+ ms.Dismiss();
+
free(hash_name);
hash_name = NULL;
diff --git a/screen_ui.cpp b/screen_ui.cpp
index f23affa..a64bad6 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -68,6 +68,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() :
text_top_(0),
show_text(false),
show_text_ever(false),
+ dialog_icon(NONE),
+ dialog_text(nullptr),
menu_(nullptr),
show_menu(false),
menu_items(0),
@@ -80,7 +82,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() :
rainbow(false),
wrap_count(0) {
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < NR_ICONS; i++) {
backgroundIcon[i] = nullptr;
}
pthread_mutex_init(&updateMutex, nullptr);
@@ -202,6 +204,9 @@ void ScreenRecoveryUI::SetColor(UIElement e) {
case TEXT_FILL:
gr_color(0, 0, 0, 160);
break;
+ case ERROR_TEXT:
+ gr_color(255, 0, 0, 255);
+ break;
default:
gr_color(255, 255, 255, 255);
break;
@@ -237,6 +242,43 @@ static const char* LONG_PRESS_HELP[] = {
NULL
};
+void ScreenRecoveryUI::draw_dialog()
+{
+ int x, y, w, h;
+
+ draw_background_locked(dialog_icon);
+
+ int iconHeight = gr_get_height(backgroundIcon[dialog_icon]);
+
+ x = (gr_fb_width()/2 - (char_width*strlen(dialog_text))/2);
+ y = (gr_fb_height()/2 + iconHeight/2);
+
+ SetColor(ERROR_TEXT);
+ gr_text(x, y, dialog_text, 0);
+
+ if (dialog_icon == ERROR) {
+ /*
+ * This could be improved...
+ *
+ * Draw rect around text "Okay".
+ * Text is centered horizontally.
+ * Bottom of text is 4 lines from bottom of screen.
+ * Rect width 4px
+ * Rect padding 8px
+ */
+ w = char_width*4;
+ h = char_height;
+ x = gr_fb_width()/2 - w/2;
+ y = gr_fb_height() - h - 4*char_height;
+ SetColor(HEADER);
+ gr_fill(x-(4+8), y-(4+8), x+w+(4+8), y+h+(4+8));
+ SetColor(MENU_SEL_BG);
+ gr_fill(x-8, y-8, x+w+8, y+h+8);
+ SetColor(MENU_SEL_FG);
+ gr_text(x, y, "Okay", 0);
+ }
+}
+
// Redraw everything on the screen. Does not flip pages.
// Should only be called with updateMutex locked.
void ScreenRecoveryUI::draw_screen_locked() {
@@ -244,6 +286,12 @@ void ScreenRecoveryUI::draw_screen_locked() {
draw_background_locked(currentIcon);
draw_progress_locked();
} else {
+
+ if (DialogShowing()) {
+ draw_dialog();
+ return;
+ }
+
gr_color(0, 0, 0, 255);
gr_clear();
@@ -416,6 +464,7 @@ void ScreenRecoveryUI::Init() {
LoadBitmapArray("icon_installing", &installing_frames, &installation);
backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr;
backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
+ LoadBitmap("icon_info", &backgroundIcon[INFO]);
LoadBitmap("icon_error", &backgroundIcon[ERROR]);
backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
@@ -653,6 +702,35 @@ void ScreenRecoveryUI::ShowFile(const char* filename) {
text_top_ = old_text_top;
}
+void ScreenRecoveryUI::DialogShowInfo(const char* text)
+{
+ pthread_mutex_lock(&updateMutex);
+ free(dialog_text);
+ dialog_text = strdup(text);
+ dialog_icon = INFO;
+ update_screen_locked();
+ pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::DialogShowError(const char* text)
+{
+ pthread_mutex_lock(&updateMutex);
+ free(dialog_text);
+ dialog_text = strdup(text);
+ dialog_icon = ERROR;
+ update_screen_locked();
+ pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::DialogDismiss()
+{
+ pthread_mutex_lock(&updateMutex);
+ free(dialog_text);
+ dialog_text = NULL;
+ update_screen_locked();
+ pthread_mutex_unlock(&updateMutex);
+}
+
void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
int initial_selection) {
pthread_mutex_lock(&updateMutex);
diff --git a/screen_ui.h b/screen_ui.h
index 2ec5f5d..e198503 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -52,6 +52,12 @@ class ScreenRecoveryUI : public RecoveryUI {
void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3);
void ShowFile(const char* filename);
+ void DialogShowInfo(const char* text);
+ void DialogShowError(const char* text);
+ int DialogShowing() const { return (dialog_text != NULL); }
+ bool DialogDismissable() const { return (dialog_icon == ERROR); }
+ void DialogDismiss();
+
// menu display
void StartMenu(const char* const * headers, const char* const * items,
int initial_selection);
@@ -63,7 +69,7 @@ class ScreenRecoveryUI : public RecoveryUI {
void Redraw();
enum UIElement {
- HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO
+ HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO, ERROR_TEXT
};
void SetColor(UIElement e);
@@ -74,8 +80,8 @@ class ScreenRecoveryUI : public RecoveryUI {
bool rtl_locale;
pthread_mutex_t updateMutex;
- GRSurface* backgroundIcon[5];
- GRSurface* backgroundText[5];
+ GRSurface* backgroundIcon[NR_ICONS];
+ GRSurface* backgroundText[NR_ICONS];
GRSurface** installation;
GRSurface* progressBarEmpty;
GRSurface* progressBarFill;
@@ -99,6 +105,9 @@ class ScreenRecoveryUI : public RecoveryUI {
bool show_text;
bool show_text_ever; // has show_text ever been true?
+ Icon dialog_icon;
+ char *dialog_text;
+
char** menu_;
const char* const* menu_headers_;
bool show_menu;
@@ -121,6 +130,7 @@ class ScreenRecoveryUI : public RecoveryUI {
void draw_background_locked(Icon icon);
void draw_progress_locked();
+ void draw_dialog();
void draw_screen_locked();
void update_screen_locked();
void update_progress_locked();
diff --git a/ui.cpp b/ui.cpp
index 38a8099..a5896ce 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -27,6 +27,7 @@
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
+#include <sys/epoll.h>
#include <cutils/properties.h>
#include <cutils/android_reboot.h>
@@ -40,8 +41,83 @@
#include "voldclient.h"
+#include "messagesocket.h"
+
#define UI_WAIT_KEY_TIMEOUT_SEC 120
+static int string_split(char* s, char** fields, int maxfields)
+{
+ int n = 0;
+ while (n+1 < maxfields) {
+ char* p = strchr(s, ' ');
+ if (!p)
+ break;
+ *p = '\0';
+ printf("string_split: field[%d]=%s\n", n, s);
+ fields[n++] = s;
+ s = p+1;
+ }
+ fields[n] = s;
+ printf("string_split: last field[%d]=%s\n", n, s);
+ return n+1;
+}
+
+static int message_socket_client_event(int fd, uint32_t epevents, void *data)
+{
+ MessageSocket* client = (MessageSocket*)data;
+
+ printf("message_socket client event\n");
+ if (!(epevents & EPOLLIN)) {
+ return 0;
+ }
+
+ char buf[256];
+ ssize_t nread;
+ nread = client->Read(buf, sizeof(buf));
+ if (nread <= 0) {
+ ev_del_fd(fd);
+ self->DialogDismiss();
+ client->Close();
+ delete client;
+ return 0;
+ }
+
+ printf("message_socket client message <%s>\n", buf);
+
+ // Parse the message. Right now we support:
+ // dialog show <string>
+ // dialog dismiss
+ char* fields[3];
+ int nfields;
+ nfields = string_split(buf, fields, 3);
+ printf("fields=%d\n", nfields);
+ if (nfields < 2)
+ return 0;
+ printf("field[0]=%s, field[1]=%s\n", fields[0], fields[1]);
+ if (strcmp(fields[0], "dialog") == 0) {
+ if (strcmp(fields[1], "show") == 0 && nfields > 2) {
+ self->DialogShowInfo(fields[2]);
+ }
+ if (strcmp(fields[1], "dismiss") == 0) {
+ self->DialogDismiss();
+ }
+ }
+
+ return 0;
+}
+
+static int message_socket_listen_event(int fd, uint32_t epevents, void *data)
+{
+ MessageSocket* ms = (MessageSocket*)data;
+ MessageSocket* client = ms->Accept();
+ printf("message_socket_listen_event: event on %d\n", fd);
+ if (client) {
+ printf("message_socket client connected\n");
+ ev_add_fd(client->fd(), message_socket_client_event, client);
+ }
+ return 0;
+}
+
RecoveryUI::RecoveryUI()
: key_queue_len(0),
key_last_down(-1),
@@ -86,6 +162,9 @@ static void* InputThreadLoop(void*) {
void RecoveryUI::Init() {
ev_init(InputCallback, this);
+ message_socket.ServerInit();
+ ev_add_fd(message_socket.fd(), message_socket_listen_event, &message_socket);
+
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
@@ -212,6 +291,12 @@ void RecoveryUI::time_key(int key_code, int count) {
}
void RecoveryUI::EnqueueKey(int key_code) {
+ if (DialogShowing()) {
+ if (DialogDismissable()) {
+ DialogDismiss();
+ }
+ return;
+ }
pthread_mutex_lock(&key_queue_mutex);
const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
if (key_queue_len < queue_max) {
diff --git a/ui.h b/ui.h
index ecf221b..123dc34 100644
--- a/ui.h
+++ b/ui.h
@@ -21,6 +21,7 @@
#include <pthread.h>
#include <time.h>
+#include "messagesocket.h"
#include "voldclient.h"
// Abstract class for controlling the user interface during recovery.
@@ -39,7 +40,7 @@ class RecoveryUI {
virtual void SetLocale(const char* locale) = 0;
// Set the overall recovery state ("background image").
- enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
+ enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, INFO, ERROR, NR_ICONS };
virtual void SetBackground(Icon icon) = 0;
// --- progress indicator ---
@@ -71,6 +72,12 @@ class RecoveryUI {
virtual void ShowFile(const char* filename) = 0;
+ virtual void DialogShowInfo(const char* text) = 0;
+ virtual void DialogShowError(const char* text) = 0;
+ virtual int DialogShowing() const = 0;
+ virtual bool DialogDismissable() const = 0;
+ virtual void DialogDismiss() = 0;
+
// --- key handling ---
// Wait for a key and return it. May return -1 after timeout.
@@ -160,6 +167,8 @@ private:
pthread_t input_thread_;
+ MessageSocket message_socket;
+
void OnKeyDetected(int key_code);
static int InputCallback(int fd, uint32_t epevents, void* data);
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 21633dc..ed3dce1 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -149,6 +149,12 @@ class FakeUI : public RecoveryUI {
}
void ShowFile(const char*) { }
+ virtual void DialogShowInfo(const char* text) {}
+ virtual void DialogShowError(const char* text) {}
+ virtual int DialogShowing() const { return 0; }
+ bool DialogDismissable() const { return false; }
+ virtual void DialogDismiss() {}
+
void StartMenu(const char* const * headers, const char* const * items,
int initial_selection) { }
int SelectMenu(int sel) { return 0; }
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 55b7afc..4a07f39 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -81,7 +81,7 @@ WearRecoveryUI::WearRecoveryUI() :
menu_items(0),
menu_sel(0) {
- for (size_t i = 0; i < 5; i++)
+ for (size_t i = 0; i < NR_ICONS; i++)
backgroundIcon[i] = NULL;
pthread_mutex_init(&updateMutex, NULL);
diff --git a/wear_ui.h b/wear_ui.h
index 839a264..d7624fe 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -91,7 +91,7 @@ class WearRecoveryUI : public RecoveryUI {
bool rtl_locale;
pthread_mutex_t updateMutex;
- GRSurface* backgroundIcon[5];
+ GRSurface* backgroundIcon[NR_ICONS];
GRSurface* *introFrames;
GRSurface* *loopFrames;