diff --git a/evoke/Crash.cpp b/evoke/Crash.cpp new file mode 100644 index 0000000..d9256a8 --- /dev/null +++ b/evoke/Crash.cpp @@ -0,0 +1,123 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#include "icons/core.xpm" +#include "Crash.h" +#include "Spawn.h" +#include +#include +#include +#include + +#include // snprintf + +#define DIALOG_W 380 +#define DIALOG_H 130 +#define DIALOG_W_EXPANDED 380 +#define DIALOG_H_EXPANDED 340 + +void show_details_cb(Fl_Widget*, void* cd) { + CrashDialog* c = (CrashDialog*)cd; + c->show_details(); +} + +void close_cb(Fl_Widget*, void* cd) { + CrashDialog* c = (CrashDialog*)cd; + c->hide(); +} + +void dummy_timeout(void*) { } + +CrashDialog::CrashDialog() : Fl_Window(DIALOG_W, DIALOG_H, _("World is going down...")) { + trace_loaded = 0; + + begin(); + pix = new Fl_Pixmap(core_xpm); + + icon_box = new Fl_Box(10, 10, 70, 75); + icon_box->image(pix); + + txt_box = new Fl_Box(85, 10, 285, 75); + txt_box->align(FL_ALIGN_WRAP | FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + close = new Fl_Button(280, 95, 90, 25, _("&Close")); + close->callback(close_cb, this); + + details = new Fl_Button(10, 95, 265, 25, _("@> Show backtrace")); + details->box(FL_FLAT_BOX); + details->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT); + details->callback(show_details_cb, this); + + // widgets for expanded dialog + trace_log = new Fl_Text_Display(10, 130, 360, 165); + trace_buff = new Fl_Text_Buffer(); + trace_log->buffer(trace_buff); + trace_log->hide(); + save_as = new Fl_Button(280, 305, 90, 25, _("&Save As...")); + save_as->hide(); + copy = new Fl_Button(185, 305, 90, 25, _("&Copy")); + copy->hide(); + end(); +} + +CrashDialog::~CrashDialog() { + // looks like fltk does not clean image() assigned data + delete pix; +} + +void CrashDialog::show_details(void) { + if(trace_log->visible()) { + trace_log->hide(); + save_as->hide(); + copy->hide(); + details->label(_("@> Show backtrace")); + size(DIALOG_W, DIALOG_H); + } else { + trace_log->show(); + save_as->show(); + copy->show(); + details->label(_("@< Hide backtrace")); + size(DIALOG_W_EXPANDED, DIALOG_H_EXPANDED); + + if(!trace_loaded) { + trace_buff->remove(0, trace_buff->length()); + + // read core + spawn_backtrace(cmd.c_str(), "core", "/tmp/gdb_output", "/tmp/gdb_script"); + + trace_buff->appendfile("/tmp/gdb_output"); + trace_loaded = 1; + + // delete core + edelib::file_remove("core"); + } + } +} + +void CrashDialog::set_data(const char* command) { + cmd = command; + + char txt[1024]; + snprintf(txt, sizeof(txt), _("Program just crashed !!!\n\nYou can inspect details about this crash by clicking on 'Show backtrace' below")); + txt_box->copy_label(txt); + trace_loaded = 0; +} + +void CrashDialog::run(void) { + if(!shown()) { + set_modal(); + show(); + } + + while(shown()) + Fl::wait(); +} diff --git a/evoke/Crash.h b/evoke/Crash.h new file mode 100644 index 0000000..86434ff --- /dev/null +++ b/evoke/Crash.h @@ -0,0 +1,50 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#ifndef __CRASH_H__ +#define __CRASH_H__ + +#include +#include +#include +#include +#include +#include + +#include + +class CrashDialog : public Fl_Window { + private: + Fl_Pixmap* pix; + Fl_Box* txt_box; + Fl_Box* icon_box; + Fl_Button* close; + Fl_Button* details; + + Fl_Text_Display* trace_log; + Fl_Text_Buffer* trace_buff; + Fl_Button* save_as; + Fl_Button* copy; + + edelib::String cmd; + bool trace_loaded; + + public: + CrashDialog(); + ~CrashDialog(); + void show_details(void); + + void set_data(const char* command); + void run(void); +}; + +#endif diff --git a/evoke/EvokeService.cpp b/evoke/EvokeService.cpp index 4a95547..14ec109 100644 --- a/evoke/EvokeService.cpp +++ b/evoke/EvokeService.cpp @@ -15,6 +15,7 @@ #include "EvokeService.h" #include "Splash.h" #include "Spawn.h" +#include "Crash.h" #include #include @@ -84,6 +85,10 @@ int get_string_property_value(Atom at, char* txt, int txt_len) { return 1; } +void service_watcher_cb(int pid, int signum) { + EvokeService::instance()->service_watcher(pid, signum); +} + EvokeService::EvokeService() : is_running(0), logfile(NULL), pidfile(NULL), lockfile(NULL) { top_win = NULL; } @@ -101,6 +106,8 @@ EvokeService::~EvokeService() { edelib::file_remove(pidfile); free(pidfile); } + + processes.clear(); } EvokeService* EvokeService::instance(void) { @@ -243,6 +250,7 @@ bool EvokeService::init_splash(const char* config, bool no_splash, bool dry_run) void EvokeService::init_autostart(void) { edelib::String home = edelib::user_config_dir(); home += "/autostart/"; + // TODO } void EvokeService::setup_atoms(Display* d) { @@ -315,6 +323,77 @@ void EvokeService::quit_x11(void) { #endif } +/* + * Monitor starting service and report if staring + * failed. Also if one of runned services crashed + * attach gdb on it pid and run backtrace. + */ +void EvokeService::service_watcher(int pid, int signum) { + //if(signum == 11) { + if(signum == 139) { + EvokeProcess pc; + if(find_and_unregister_process(pid, pc)) { + printf("%s crashed with core dump\n", pc.cmd.c_str()); + + CrashDialog cdialog; + cdialog.set_data(pc.cmd.c_str()); + cdialog.run(); + return; + } + } else if(signum == 32512) { + fl_alert("No such file"); + } + + unregister_process(pid); +} + +void EvokeService::register_process(const char* cmd, pid_t pid) { + EvokeProcess pc; + pc.cmd = cmd; + pc.pid = pid; + printf("registering %s with %i\n", cmd, pid); + processes.push_back(pc); +} + +void EvokeService::unregister_process(pid_t pid) { + if(processes.empty()) + return; + + ProcessListIter it = processes.begin(); + ProcessListIter it_end = processes.end(); + + while(it != it_end) { + if((*it).pid == pid) { + printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid); + processes.erase(it); + return; + } + ++it; + } +} + +bool EvokeService::find_and_unregister_process(pid_t pid, EvokeProcess& pc) { + if(processes.empty()) + return 0; + + ProcessListIter it = processes.begin(); + ProcessListIter it_end = processes.end(); + + while(it != it_end) { + if((*it).pid == pid) { + printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid); + pc.cmd = (*it).cmd; + pc.pid = pid; + + processes.erase(it); + return 1; + } + ++it; + } + + return 0; +} + int EvokeService::handle(const XEvent* ev) { logfile->printf("Got event %i\n", ev->type); @@ -333,16 +412,20 @@ int EvokeService::handle(const XEvent* ev) { } else if(ev->type == PropertyNotify) { if(ev->xproperty.atom == _ede_spawn) { char buff[1024]; - if(get_string_property_value(_ede_spawn, buff, sizeof(buff))) { logfile->printf("Got _EVOKE_SPAWN with %s. Starting client...\n", buff); - int r = spawn_program(buff); + + pid_t child; + int r = spawn_program_with_core(buff, service_watcher_cb, &child); if(r != 0) fl_alert("Unable to start %s. Got code %i", buff, r); - } else - logfile->printf("Got _EVOKE_SPAWN with malformed data. Ignoring...\n"); + else + register_process(buff, child); + } else { + logfile->printf("Got _EVOKE_SPAWN with malformed data. Ignoring...\n"); + } return 1; } diff --git a/evoke/EvokeService.h b/evoke/EvokeService.h index 144da61..3670a36 100644 --- a/evoke/EvokeService.h +++ b/evoke/EvokeService.h @@ -24,11 +24,20 @@ struct EvokeClient { edelib::String exec; // program name/path to run }; -typedef edelib::list ClientList; -typedef edelib::list::iterator ClientListIter; +struct EvokeProcess { + edelib::String cmd; + pid_t pid; +}; + +typedef edelib::list ClientList; +typedef edelib::list::iterator ClientListIter; + typedef edelib::list StringList; typedef edelib::list::iterator StringListIter; +typedef edelib::list ProcessList; +typedef edelib::list::iterator ProcessListIter; + class Fl_Double_Window; class EvokeService { @@ -44,7 +53,8 @@ class EvokeService { Atom _ede_spawn; Atom _ede_evoke_quit; - ClientList clients; + ClientList clients; + ProcessList processes; public: EvokeService(); @@ -68,6 +78,12 @@ class EvokeService { void register_top(Fl_Double_Window* win) { top_win = win; } void unregister_top(void) { top_win = NULL; } + void register_process(const char* cmd, pid_t pid); + void unregister_process(pid_t pid); + bool find_and_unregister_process(pid_t pid, EvokeProcess& pc); + + void service_watcher(int pid, int signum); + void quit_x11(void); }; diff --git a/evoke/Jamfile b/evoke/Jamfile index a2eefe1..2942e90 100644 --- a/evoke/Jamfile +++ b/evoke/Jamfile @@ -10,9 +10,11 @@ SubDir TOP evoke ; -SOURCE = evoke.cpp EvokeService.cpp Spawn.cpp Splash.cpp Log.cpp Logout.cpp ; +SOURCE = evoke.cpp EvokeService.cpp Spawn.cpp Splash.cpp Log.cpp Logout.cpp Crash.cpp ; EdeProgram evoke : $(SOURCE) ; FltkProgramBare test/evoke_test : test/evoke_test.cpp ; #TranslationStrings locale : $(SOURCE) ; EdeManual doc/evoke.txt ; + +#EdeProgram crash : Crash.cpp ; diff --git a/evoke/Spawn.cpp b/evoke/Spawn.cpp index e7ba246..890a520 100644 --- a/evoke/Spawn.cpp +++ b/evoke/Spawn.cpp @@ -18,23 +18,54 @@ #include #include #include +#include + +#include + +#include // getrlimit, setrlimit +#include // extern char** environ; +// FIXME: is this safe ??? (or to use somehow sig_atomic_t) +SignalWatch* global_watch = 0; -int spawn_program(const char* cmd) { +void sigchld_handler(int sig) { + int pid, status; + do { + errno = 0; + pid = waitpid(WAIT_ANY, &status, WNOHANG); + + if(global_watch != 0) + global_watch(pid, status); + + } while(pid <= 0 && errno == EINTR); +} + +int spawn_program(const char* cmd, SignalWatch wf, pid_t* child_pid_ret) { if(!cmd) return SPAWN_EMPTY; int nulldev = -1; int status_ret = 0; - + + if(wf) { + struct sigaction sa; + sa.sa_handler = sigchld_handler; + sa.sa_flags = SA_NOCLDSTOP; + //sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, (struct sigaction*)0); + + global_watch = wf; + } + pid_t pid = fork(); if(pid == -1) return SPAWN_FORK_FAILED; - // run the child if(pid == 0) { + // this is child char* argv[4]; argv[0] = "/bin/sh"; argv[1] = "-c"; @@ -53,12 +84,268 @@ int spawn_program(const char* cmd) { close(1); dup(nulldev); close(2); dup(nulldev); + if(execve(argv[0], argv, environ) == -1) { close(nulldev); // should not get here return SPAWN_EXECVE_FAILED; } - } + } + + if(nulldev != -1) + close(nulldev); + /* + * Record child pid; it is returned by fork(), but since this + * function does not wait until child quits, it will return + * immediately, filling (if) requested child pid + */ + if(child_pid_ret) + *child_pid_ret = pid; return status_ret; } + +int spawn_program_with_core(const char* cmd, SignalWatch* wf, pid_t* child_pid_ret) { + struct rlimit r; + if(getrlimit(RLIMIT_CORE, &r) == -1) + return -1; + + rlim_t old = r.rlim_cur; + r.rlim_cur = RLIM_INFINITY; + + // FIXME: add own core limit ? + if(setrlimit(RLIMIT_CORE, &r) == -1) + return -1; + + int ret = spawn_program(cmd, wf, child_pid_ret); + + r.rlim_cur = old; + if(setrlimit(RLIMIT_CORE, &r) == -1) + return -1; + + return ret; +} + +int spawn_backtrace(const char* program, const char* core_path, const char* output, const char* script) { + const char* gdb_script = "bt\nquit\n"; + const int gdb_script_len = 8; + + // file with gdb commands + int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770); + if(sfd == -1) + return -1; + write(sfd, gdb_script, gdb_script_len); + close(sfd); + + // output file with gdb backtrace + int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770); + if(ofd == -1) + return -1; + + pid_t pid = fork(); + + if(pid == -1) { + close(ofd); + return -1; + } else if(pid == 0) { + dup2(ofd, 1); + close(ofd); + + char* argv[8]; + argv[0] = "gdb"; + argv[1] = "--quiet"; + argv[2] = "--batch"; + argv[3] = "-x"; + argv[4] = (char*)script; + argv[5] = (char*)program; + argv[6] = (char*)core_path; + argv[7] = 0; + + //printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); + + execvp(argv[0], argv); + return -1; + } else { + int status; + if(waitpid(pid, &status, 0) != pid) + return -1; + } + + return 0; +} + +#if 0 +int spawn_backtrace(int crash_pid, const char* output, const char* script) { + const char* gdb_script = "info threads\nthread apply all bt full\nquit\n"; + const int gdb_script_len = 43; + //const char* gdb_script = "bt\nquit\n"; + //const int gdb_script_len = 8; + + pid_t parent_pid, child_pid; + + //kill(crash_pid, SIGCONT); + + char parent_pid_str[64]; + parent_pid = crash_pid; + kill(parent_pid, SIGCONT); + + //parent_pid = getpid(); + sprintf(parent_pid_str, "%ld", (long)parent_pid); + + // file with gdb commands + int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770); + if(sfd == -1) + return -1; + write(sfd, gdb_script, gdb_script_len); + close(sfd); + + // output file with gdb backtrace + int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770); + if(ofd == -1) + return -1; + + child_pid = fork(); + + if(child_pid == -1) + return -1; + else if(child_pid == 0) { + //dup2(ofd, 1); + close(ofd); + + char* argv[8]; + argv[0] = "gdb"; + argv[1] = "--quiet"; + argv[2] = "--batch"; + argv[3] = "-x"; + argv[4] = (char*)script; + argv[5] = "--pid"; + argv[6] = parent_pid_str; + argv[7] = 0; + + //argv[5] = "--pid"; + //argv[6] = parent_pid_str; + //argv[7] = 0; + + printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); + + execvp(argv[0], argv); + return -1; + } else { + /* + int status; + waitpid(child_pid, &status, 0); + */ + } + + //unlink(script); + return 0; +} + +#if 0 +int spawn_backtrace(const char* output, const char* script){ + pid_t pid, parent_pid; + int ifd[2], ofd[2], efd[2]; + + const char* gdb_script = "info threads\nthread apply all by\nquit\n"; + const int gdb_script_len = 38; + + puts("xxxx"); + + // write script for gdb + int fds = open(script, O_WRONLY | O_CREAT | O_TRUNC, 0770); + if(fds == -1) + return -1; + write(fds, gdb_script, gdb_script_len); + close(fds); + + puts("yyyyy"); + + errno = 0; + // open output for gdb trace + int fdo = open(output, O_WRONLY, O_CREAT | O_TRUNC, 0770); + if(fdo == -1) { + printf("can't open fdo(%s): %s\n", output, strerror(errno)); + return -1; + } + + parent_pid = getppid(); + char parent_pid_str[64]; + sprintf(parent_pid_str, "%d", parent_pid); + + char* argv[8]; + argv[0] = "gdb"; + argv[1] = "--quiet"; + argv[2] = "--batch"; + argv[3] = "-x"; + argv[4] = (char*)script; + argv[5] = "--pid"; + argv[6] = parent_pid_str; + argv[7] = 0; + + puts("ayyyy"); + + if(pipe(ifd) == -1) { + printf("cant open ifd pipe: %s\n", strerror(errno)); + } + + if(pipe(ofd) == -1) { + printf("cant open ofd pipe: %s\n", strerror(errno)); + } + + if(pipe(efd) == -1) { + printf("cant open efd pipe: %s\n", strerror(errno)); + } + + pid = fork(); + + if(pid == -1) { + close(ifd[0]); close(ifd[1]); + close(ofd[0]); close(ofd[1]); + close(efd[0]); close(efd[1]); + return -1; + } else if(pid == 0) { + // child + printf("gdb pid is %i\n", getpid()); + printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); + + close(ifd[1]); + fclose(stdin); + dup(ifd[0]); + + close(ofd[0]); + fclose(stdout); + dup(ofd[1]); + + close(efd[0]); + fclose(stderr); + dup(efd[1]); + + execvp(argv[0], (char**)argv); + return -1; + } else { + // parent + fclose(stdin); + + close(ifd[0]); + close(ofd[1]); + close(efd[1]); + + write(fdo, "The trace:\n", 11); + + close(ifd[1]); + close(ofd[0]); + close(efd[0]); + + close(fdo); + + kill(pid, SIGKILL); + + waitpid(pid, NULL, 0); + //_exit(0); + } + + return 0; +} +#endif + +#endif diff --git a/evoke/Spawn.h b/evoke/Spawn.h index 93fb6d5..2bafebd 100644 --- a/evoke/Spawn.h +++ b/evoke/Spawn.h @@ -13,6 +13,8 @@ #ifndef __SPAWN_H__ #define __SPAWN_H__ +#include // pid_t + /* * This is little bit modified code from edelib run_program() * so evoke specific stuff can be added. Also, possible option @@ -27,7 +29,14 @@ #define SPAWN_EXECVE_FAILED 65530 // internal execve failed #define SPAWN_PTY_FAILED 65529 // TODO #define SPAWN_USER_CANCELED 65528 // TODO +#define SPAWN_CRASHED 65527 // executable crashed +#define SPAWN_KILLED 65526 // executable crashed +#define SPAWN_NOEXITED 65525 -int spawn_program(const char* cmd); +typedef void (SignalWatch)(int pid, int status); + +int spawn_program(const char* cmd, SignalWatch* wf = 0, pid_t* child_pid_ret = 0); +int spawn_program_with_core(const char* cmd, SignalWatch* wf = 0, pid_t* child_pid_ret = 0); +int spawn_backtrace(const char* program, const char* core_path, const char* output, const char* script); #endif diff --git a/evoke/fl/crash.fl b/evoke/fl/crash.fl new file mode 100644 index 0000000..45a973a --- /dev/null +++ b/evoke/fl/crash.fl @@ -0,0 +1,28 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0108 +header_name {.h} +code_name {.cxx} +Function {} {open +} { + Fl_Window {} {open + xywh {354 160 380 130} type Double visible + } { + Fl_Box {} { + image {icons/core.xpm} xywh {10 10 70 75} labelsize 14 + } + Fl_Box {} { + label {Program foo just crashed !!! + +You can inspect details about this crash by clicking on 'Show details' below} selected + xywh {85 10 285 75} align 148 + } + Fl_Button {} { + label {&Close} + xywh {280 95 90 25} + } + Fl_Button {} { + label {@> Show details} + xywh {10 95 265 25} box FLAT_BOX align 20 + } + } +} diff --git a/evoke/fl/crash_expanded.fl b/evoke/fl/crash_expanded.fl new file mode 100644 index 0000000..1d41e22 --- /dev/null +++ b/evoke/fl/crash_expanded.fl @@ -0,0 +1,39 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0108 +header_name {.h} +code_name {.cxx} +Function {} {open selected +} { + Fl_Window {} {open + xywh {354 160 380 340} type Double visible + } { + Fl_Box {} { + image {icons/core.xpm} xywh {10 10 70 75} labelsize 14 + } + Fl_Box {} { + label {Program foo just crashed !!! + +You can inspect details about this crash by clicking on 'Show details' below} + xywh {85 10 285 75} align 148 + } + Fl_Button {} { + label {&Close} + xywh {280 95 90 25} + } + Fl_Button {} { + label {@< Hide details} + xywh {10 95 265 25} box FLAT_BOX align 20 + } + Fl_Text_Display {} { + xywh {10 130 360 165} labelsize 14 textsize 14 + } + Fl_Button {} { + label {&Save As...} + xywh {280 305 90 25} + } + Fl_Button {} { + label {&Copy} + xywh {185 305 90 25} + } + } +} diff --git a/evoke/logout.fl b/evoke/fl/logout.fl similarity index 100% rename from evoke/logout.fl rename to evoke/fl/logout.fl diff --git a/evoke/icons/core.xpm b/evoke/icons/core.xpm new file mode 100644 index 0000000..da539ee --- /dev/null +++ b/evoke/icons/core.xpm @@ -0,0 +1,416 @@ +/* XPM */ +static char * core_xpm[] = { +"48 48 365 2", +" c None", +". c #FAE601", +"+ c #FCF001", +"@ c #FEF800", +"# c #F9E202", +"$ c #F2BE04", +"% c #F2BD04", +"& c #F5CC03", +"* c #FBE901", +"= c #FBEC01", +"- c #EB9A06", +"; c #EEAA05", +"> c #FEFC00", +", c #F7D506", +"' c #E78808", +") c #E88D06", +"! c #D1B46D", +"~ c #C7AF75", +"{ c #C2994A", +"] c #F1BB04", +"^ c #F5CF03", +"/ c #FCEE01", +"( c #EDD426", +"_ c #DCBC76", +": c #E3C06E", +"< c #CCAB4F", +"[ c #E7C16F", +"} c #ECC36B", +"| c #CCB34C", +"1 c #FCFB04", +"2 c #D1C652", +"3 c #D0B76B", +"4 c #DDBA6C", +"5 c #DAB96E", +"6 c #CFBB40", +"7 c #A7A7A7", +"8 c #A8A8A8", +"9 c #A9A9A9", +"0 c #A4A4A4", +"a c #9F9F9F", +"b c #999999", +"c c #919191", +"d c #A5B4BE", +"e c #E7E7E1", +"f c #C3C2C7", +"g c #B3AB9B", +"h c #B5A679", +"i c #C5AF6A", +"j c #D6B86A", +"k c #CDB16B", +"l c #C9B05B", +"m c #B7A27C", +"n c #EBDC19", +"o c #AFAFAF", +"p c #B8B8B8", +"q c #BBBBBB", +"r c #BCBCBC", +"s c #BABABA", +"t c #B4B4B4", +"u c #888888", +"v c #7B7B7B", +"w c #102032", +"x c #808285", +"y c #FBF5F2", +"z c #D9D0BF", +"A c #C6AD71", +"B c #DFBB69", +"C c #D9B86B", +"D c #C0AC7C", +"E c #BBA785", +"F c #AAAAAA", +"G c #ADADAD", +"H c #D6D6D6", +"I c #EBEBEB", +"J c #FCFCFC", +"K c #FEFEFE", +"L c #FFFFFF", +"M c #FBFBFB", +"N c #F5F5F5", +"O c #E7E7E7", +"P c #D2D2D2", +"Q c #868686", +"R c #636363", +"S c #616161", +"T c #6E6E6E", +"U c #3E3E3D", +"V c #3B3A3A", +"W c #161311", +"X c #65605D", +"Y c #C3BCB7", +"Z c #D8CFB0", +"` c #D1B873", +" . c #DFBD6A", +".. c #C8B37E", +"+. c #AAAEBF", +"@. c #FFFD00", +"#. c #B1B1B1", +"$. c #CACACA", +"%. c #E9E9E9", +"&. c #F0F0F0", +"*. c #E5E5E5", +"=. c #E3E3E3", +"-. c #E1E1E1", +";. c #DFDFDF", +">. c #898989", +",. c #3C3C3C", +"'. c #393939", +"). c #262626", +"!. c #1D1D1E", +"~. c #0F0F0F", +"{. c #5A5857", +"]. c #5A5955", +"^. c #4D4D45", +"/. c #918E7A", +"(. c #D3CBA7", +"_. c #E5DCB3", +":. c #E0DECD", +"<. c #AEAEAE", +"[. c #D3D3D3", +"}. c #FAFAFA", +"|. c #FDFDFD", +"1. c #EFEFEF", +"2. c #EAEAEA", +"3. c #DDDDDD", +"4. c #D7D7D7", +"5. c #D0D0D0", +"6. c #C9C9C9", +"7. c #C1C1C1", +"8. c #BDBDBD", +"9. c #7D7D7D", +"0. c #333333", +"a. c #131313", +"b. c #4F4F4F", +"c. c #484848", +"d. c #5F5F60", +"e. c #393C3A", +"f. c #080C09", +"g. c #1F1F1B", +"h. c #817F74", +"i. c #D8D6C6", +"j. c #F2F2E8", +"k. c #DFE7F0", +"l. c #F9F9F9", +"m. c #F3F3F3", +"n. c #EDEDED", +"o. c #E8E8E8", +"p. c #E4E4E4", +"q. c #DEDEDE", +"r. c #CCCCCC", +"s. c #C3C3C3", +"t. c #B9B9B9", +"u. c #ABABAB", +"v. c #A1A1A1", +"w. c #151515", +"x. c #4B4B4B", +"y. c #6B6B6B", +"z. c #3F3F3F", +"A. c #1B1B1B", +"B. c #121211", +"C. c #050604", +"D. c #000000", +"E. c #0C0707", +"F. c #433F37", +"G. c #7E7F79", +"H. c #F7F7F7", +"I. c #ECECEC", +"J. c #E2E2E2", +"K. c #DCDCDC", +"L. c #B0B0B0", +"M. c #A5A5A5", +"N. c #979797", +"O. c #969696", +"P. c #434343", +"Q. c #191919", +"R. c #010101", +"S. c #060001", +"T. c #0C0203", +"U. c #050002", +"V. c #0B0D14", +"W. c #EEEEEE", +"X. c #DADADA", +"Y. c #C6C6C6", +"Z. c #BEBEBE", +"`. c #B6B6B6", +" + c #9C9C9C", +".+ c #909090", +"++ c #848484", +"@+ c #6C6C6C", +"#+ c #1A1A1A", +"$+ c #060606", +"%+ c #020101", +"&+ c #010403", +"*+ c #020405", +"=+ c #010202", +"-+ c #D8D8D8", +";+ c #CFCFCF", +">+ c #C2C2C2", +",+ c #A2A2A2", +"'+ c #878787", +")+ c #787878", +"!+ c #656565", +"~+ c #252525", +"{+ c #080C0D", +"]+ c #F4F4F4", +"^+ c #D9D9D9", +"/+ c #D4D4D4", +"(+ c #CECECE", +"_+ c #C5C5C5", +":+ c #BFBFBF", +"<+ c #B2B2B2", +"[+ c #A0A0A0", +"}+ c #989898", +"|+ c #8E8E8E", +"1+ c #737373", +"2+ c #6D6D6D", +"3+ c #585858", +"4+ c #202020", +"5+ c #070707", +"6+ c #4B4C4C", +"7+ c #767676", +"8+ c #D1D1D1", +"9+ c #A3A3A3", +"0+ c #9B9B9B", +"a+ c #949494", +"b+ c #8C8C8C", +"c+ c #838383", +"d+ c #797979", +"e+ c #6F6F6F", +"f+ c #676767", +"g+ c #4A4A4A", +"h+ c #030303", +"i+ c #181818", +"j+ c #040404", +"k+ c #565656", +"l+ c #D5D5D5", +"m+ c #C7C7C7", +"n+ c #C4C4C4", +"o+ c #ACACAC", +"p+ c #9E9E9E", +"q+ c #7E7E7E", +"r+ c #666666", +"s+ c #606060", +"t+ c #575757", +"u+ c #353535", +"v+ c #383738", +"w+ c #777677", +"x+ c #494949", +"y+ c #CBCBCB", +"z+ c #B5B5B5", +"A+ c #9A9A9A", +"B+ c #939393", +"C+ c #8B8B8B", +"D+ c #7C7C7C", +"E+ c #5E5E5E", +"F+ c #595959", +"G+ c #424242", +"H+ c #171717", +"I+ c #0C0C0C", +"J+ c #5D5D5D", +"K+ c #2F2F2F", +"L+ c #8F8F8F", +"M+ c #8A8A8A", +"N+ c #858585", +"O+ c #7F7F7F", +"P+ c #757575", +"Q+ c #686868", +"R+ c #5B5B5B", +"S+ c #4D4D4D", +"T+ c #6F6D6E", +"U+ c #3B3B3B", +"V+ c #9D9D9D", +"W+ c #959595", +"X+ c #8D8D8D", +"Y+ c #828282", +"Z+ c #6A6A6A", +"`+ c #646464", +" @ c #555555", +".@ c #2E2E2E", +"+@ c #0B0B0B", +"@@ c #020202", +"#@ c #222222", +"$@ c #1D1D1D", +"%@ c #080808", +"&@ c #808080", +"*@ c #7A7A7A", +"=@ c #707070", +"-@ c #5F5F5F", +";@ c #545454", +">@ c #525252", +",@ c #2B2B2B", +"'@ c #0D0D0D", +")@ c #050505", +"!@ c #111111", +"~@ c #5A5A5A", +"{@ c #929292", +"]@ c #717171", +"^@ c #515151", +"/@ c #3D3D3D", +"(@ c #272727", +"_@ c #323232", +":@ c #6D6C6C", +"<@ c #1C1C1C", +"[@ c #535353", +"}@ c #414141", +"|@ c #0A0A0A", +"1@ c #646363", +"2@ c #161616", +"3@ c #696969", +"4@ c #313131", +"5@ c #1F1F1F", +"6@ c #0E0E0E", +"7@ c #090909", +"8@ c #121212", +"9@ c #444444", +"0@ c #464646", +"a@ c #282828", +"b@ c #5C5C5C", +"c@ c #141414", +"d@ c #212121", +"e@ c #101010", +"f@ c #616061", +"g@ c #626262", +"h@ c #232323", +"i@ c #373737", +"j@ c #5B5959", +"k@ c #1E1E1E", +"l@ c #2C2C2C", +"m@ c #505050", +"n@ c #494646", +"o@ c #303030", +"p@ c #626162", +"q@ c #292929", +"r@ c #2A2A2A", +"s@ c #616060", +"t@ c #2D2D2D", +"u@ c #575656", +"v@ c #363636", +"w@ c #3E3E3E", +"x@ c #5F5E5E", +"y@ c #3A3A3A", +"z@ c #242424", +"A@ c #343434", +"B@ c #383838", +"C@ c #474747", +"D@ c #4F4D4D", +"E@ c #737272", +"F@ c #404040", +"G@ c #5B5A5A", +"H@ c #696869", +"I@ c #4E4E4E", +"J@ c #3D3C3C", +"K@ c #5D5B5C", +"L@ c #6A696A", +"M@ c #454545", +"N@ c #5B595A", +"O@ c #6A6969", +"P@ c #515050", +"Q@ c #595859", +"R@ c #656464", +"S@ c #413F3F", +"T@ c #747373", +"U@ c #5F5E5F", +"V@ c #605F5F", +"W@ c #595858", +"X@ c #545252", +" . + ", +" @ # $ % & * ", +" = - ; $ ", +" > , ' ) ", +" > ! ~ { ] ^ ", +" / ( _ : < . ", +" [ } | ", +" 1 2 3 4 5 6 ", +" 7 8 9 0 a b c d e f g h i j k l m n ", +" 8 o p q r s t 8 b u v w x y z A B C D E ", +" F G s H I J K L M N O P t Q R S T U V W X Y Z ` ...+. @. ", +" 7 #.$.%.L L L M N &.%.*.=.-.;.$.>.,.'.).!.~.{.].^./.(._.:. ", +" <.[.}.L L |.N 1.2.=.3.4.5.6.7.8.7.s 9.0.a.b.c.d.e.f.g.h.i.j.k. ", +" #.I L L l.m.n.I o.p.q.4.[.r.s.t.u.v.G u w.x.y.z.A.B.C.D.E.F.G. ", +" #.m.L H.1.I.I.o.*.J.K.H 5.6.7.t.L.M.N.O.u P.S P.Q.R.D.S.T.U.D.V. ", +" 8 W.L &.%.o.O *.J.3.X.[.r.Y.Z.`.G 0 +.+Q ++@+0.#+$+D.R.%+&+*+=+ ", +" c q.L I.-.=.-.;.3.-+[.;+$.>+r t u.,+b c '+9.)+!+~+R.D.R.R.D.D.{+ ", +" )+L.]+o.^+X.^+H /+P (+6._+:+t.<+8 [+}+|+++v 1+2+3+4+D.D.D.D.D.5+6+ ", +" 7+8+%.4.8+8+5.(+$.Y.s.Z.p <+u.9+0+a+b+c+d+e+f+R g+w.D.D.D.h+i+j+k+ ", +" y.>.l+H 6.m+m+n+7.Z.q `.L.o+M.p+O..+u q+7+e+r+s+t+u+5+D.D.D.D.D.D.v+w+ ", +" x+a+y+_+q r q p z+<+<.9 0 a A+B+C+c+D+1+2+!+E+F+G+H+R.D.D.D.D.D.D.I+r+ ", +" J+K+L+Z.z+<.o G F 7 M.[+ +N.c M+N+O+P+e+Q+S R+F+S+~+h+D.D.D.D.D.D.D.D.c.T+ ", +" U+i+2+t 9 a [+V+A+}+W+c X+>.Y+v )+1+Z+`+J+t+ @x..@+@@@h+@@@@D.D.D.D.D.#@P+ ", +" $@%@U+O.0 B+X+|+b+M+'+c+&@*@P+=@@+`+-@F+;@>@c.,@'@j+j+j+j+j+@@D.D.D.D.)@t+ ", +"@++@D.!@~@{@.+&@O+O+v d+7+]@@+Q+!+s+3+ @^@g+/@(@'@)@$+$+j+j+$+$+h+R.D.D.D._@:@ ", +"J+@@D.D.<@~@D+D+7+1+=@T y.r+S -@R+3+[@x.}@K+#+|@)@$+$+$+)@$+5+%@%@h+D.D.D.$@1@ ", +"3+@@D.D.D.2@G+s+3@Q+r+`+S J+F+[@S+g+}@4@5@6@)@$+)@5+5+5+5+%@7@|@+@5+@@D.D.8@E+ ", +";@@@D.D.D.D.|@~+U+9@c.g+g+0@P.U+4@a@$@~.5+$+5+5+5+%@%@7@7@|@+@I+'@'@$+D.D.'@b@ ", +"b.@@D.D.D.D.R.@@+@c@$@d@#@5@<@H+!@7@$+5+5+5+%@|@+@+@7@+@I+'@6@6@e@a.6@h+D.I+~@ ", +"-@)@D.D.D.D.h+h+@@h+j+)@j+$+$+5+$+$+$+5+5+5+7@+@I+I+I+'@~.e@8@a.a.2@2@5+D.e@E+ ", +"-@+@D.D.D.D.h+@@h+j+j+)@)@$+$+5+%@%@%@%@7@|@|@+@'@'@6@~.!@!@a.w.w.2@<@7@D.a.f@ ", +"g@!@D.D.D.D.h+j+$+$+$+$+$+5+5+%@7@7@7@|@+@I+I+'@~.~.e@!@a.c@2@i+Q.#+h@I+D.#@!+ ", +" 5@@@D.D.D.$+$+$+$+5+5+5+%@7@7@|@|@|@+@I+'@e@8@8@c@2@2@H+#+A.<@$@4+a@~.D.i@j@ ", +" P.h+D.D.D.5+%@5+%@|@|@+@|@I+I+'@6@~.~.~.e@8@8@c@H+i+Q.#+<@k@4+d@).l@a.@@m@n@ ", +" Q+I+D.D.D.$+%@7@|@|@+@I+I+'@'@~.e@e@e@e@8@c@2@H+Q.A.$@k@4+h@~+).l@o@c@6@p@ ", +" ++q@@@D.D.%@|@+@I+I++@I+6@e@!@!@8@c@c@c@w.i+A.A.$@5@#@h@~+a@r@l@u+4@a._@s@ ", +" k+5+D.D.%@I+~.6@6@6@~.8@8@a.c@w.H+i+H+Q.<@5@5@d@h@).a@l@t@o@0.,.t@6@u@ ", +" =@).D.D.)@+@8@!@!@a.c@c@w.2@H+i+#+A.<@k@4+#@h@).q@,@.@_@0.v@z.w@<@4@x@ ", +" 3+6@D.R.$+8@2@w.2@H+i+Q.#+#+A.$@5@d@h@~+).a@,@.@o@0.v@y@P.x+K+2@x@ ", +" 9@j+D.@@6@Q.A.Q.Q.A.k@5@k@4+#@h@z@(@r@l@K+_@A@v@B@U+C@S+U+w.x.D@ ", +" E@i@R.D.)@8@k@#@d@5@d@h@z@~+(@q@a@l@K+4@A@B@'.,.P.x.b./@Q.F@G@ ", +" H@4@D.D.$+c@h@r@r@q@(@(@q@,@.@K+_@A@v@y@w@P.g+^@I@B@<@J@K@ ", +" L@0.)@D.)@8@#@o@0.0._@_@_@A@v@i@y@F@M@g+m@^@M@l@k@P.N@ ", +" O@z.6@D.@@|@A.r@i@w@/@w@F@}@P.0@x+x.x.P..@4+).P@Q@ ", +" F+(@+@j+j++@c@#@t@'.}@9@0@G+'..@#@H+z@^@R@S@ ", +" R@k+0.|@5+j+)@@@R.D.D.R.R.j+c@u+F+T@ ", +" U@s@;@,.z@|@'@)@5+i+d@,.E@ ", +" E+V@W@R+W@X@ "};