https://bitbucket.org/daniel_fort/magic-lantern
Raw File
Tip revision: 7e2ed7cc0d97f96eb973e496ff53f10b98fb4b7f authored by a1ex on 15 July 2014, 05:57:32 UTC
Close branch josepvm/added-missing-stubs-for-500d-gui-timers--1404550062626
Tip revision: 7e2ed7c
script.c
#define CONFIG_CONSOLE
#include <module.h>
#include <dryos.h>
#include <menu.h>
#include <bmp.h>
#include <string.h>
#include <../tcc/libtcc.h>

static void script_define_param_variables(void* tcc, void* script_state);

static int script_load_symbols(void* tcc, void* script_state, char *filename)
{
    uint32_t size = 0;
    FILE* file = NULL;
    char *buf = NULL;
    uint32_t count = 0;
    uint32_t pos = 0;

    if( FIO_GetFileSize( filename, &size ) != 0 )
    {
        console_printf("Error loading '%s': File does not exist\n", filename);
        return -1;
    }
    buf = fio_malloc(size);
    if(!buf)
    {
        console_printf("Error loading '%s': File too large\n", filename);
        return -1;
    }

    file = FIO_OpenFile(filename, O_RDONLY | O_SYNC);
    if(!file)
    {
        console_printf("Error loading '%s': File does not exist\n", filename);
        fio_free(buf);
        return -1;
    }
    FIO_ReadFile(file, buf, size);
    FIO_CloseFile(file);

    while(buf[pos])
    {
        char address_buf[16];
        char symbol_buf[128];
        uint32_t length = 0;
        uint32_t address = 0;

        while(buf[pos + length] && buf[pos + length] != ' ' && length < sizeof(address_buf))
        {
            address_buf[length] = buf[pos + length];
            length++;
        }
        address_buf[length] = '\000';

        pos += length + 1;
        length = 0;

        while(buf[pos + length] && buf[pos + length] != '\r' && buf[pos + length] != '\n' && length < sizeof(symbol_buf))
        {
            symbol_buf[length] = buf[pos + length];
            length++;
        }
        symbol_buf[length] = '\000';

        pos += length + 1;
        length = 0;

        while(buf[pos + length] && (buf[pos + length] == '\r' || buf[pos + length] == '\n'))
        {
            pos++;
        }
        sscanf(address_buf, "%x", &address);

        module_exec(tcc, "tcc_add_symbol", 3, script_state, symbol_buf, (void*)address);
        count++;
    }

    /* ToDo: parse the old plugin sections as all needed OS stubs are already described there */
    module_exec(tcc, "tcc_add_symbol", 3, script_state, "strcpy", &strcpy);
    module_exec(tcc, "tcc_add_symbol", 3, script_state, "strlen", &strlen);

    fio_free(buf);
    return 0;
}

static int tcc_compile_and_run(char* filename)
{
    console_printf("Compiling script %s...\n", filename);

    void* tcc = NULL;
    TCCState * script_state = NULL;
    void* script_buf = NULL;
    
    tcc = module_load("ML/MODULES/tcc.mo");
    if (!tcc)
    {
        console_printf("Could not load TCC compiler.\n");
        goto err;
    }
    
    script_state = (void*) module_exec(tcc, "tcc_new", 0);
    if (!script_state)
    {
        console_printf("Could not initialize TCC compiler.\n");
        goto err;
    }

    module_exec(tcc, "tcc_set_options", 2, script_state, "-nostdlib");
    module_exec(tcc, "tcc_set_options", 2, script_state, "-Wall");
    module_exec(tcc, "tcc_set_options", 2, script_state, "-IML/scripts");
    module_exec(tcc, "tcc_set_output_type", 2, script_state, TCC_OUTPUT_MEMORY);

    int ret_compile = module_exec(tcc, "tcc_add_file", 2, script_state, filename);
    if (ret_compile < 0)
    {
        console_printf("Compilation error.\n");
        goto err;
    }

    script_load_symbols(tcc, script_state, "ML/modules/5D3_113.sym");

    int size = module_exec(tcc, "tcc_relocate", 2, script_state, NULL);
    if (size <= 0)
    {
        console_printf("Linking error.\n");
        goto err;
    }

    script_buf = (void*) tcc_malloc(size);
    if (!script_buf)
    {
        console_printf("Malloc error.\n");
        goto err;
    }
    
    int ret_link = module_exec(tcc, "tcc_relocate", 2, script_state, script_buf);
    if (ret_link < 0)
    {
        console_printf("Relocate error.\n");
        goto err;
    }
        
    void (*script_main)() = (void*) module_exec(tcc, "tcc_get_symbol", 2, script_state, "main");
    if (!script_main)
    {
        console_printf("Your script should have a main function.\n");
        goto err;
    }

    script_define_param_variables(tcc, script_state);

    module_exec(tcc, "tcc_delete", 1, script_state); script_state = NULL;
    module_unload(tcc); tcc = NULL;

    console_printf("Running script %s...\n", filename);

    /* http://repo.or.cz/w/tinycc.git/commit/6ed6a36a51065060bd5e9bb516b85ff796e05f30 */
    sync_caches();

    script_main();

    tcc_free(script_buf); script_buf = NULL;
    return 0;

err:
    if (script_buf) tcc_free(script_buf);
    if (script_state) module_exec(tcc, "tcc_delete", 1, script_state);
    if (tcc) module_unload(tcc);
    return 1;
}

static void script_update_menu();
static void script_scan(const char *fn, const char * script_source_str);

static int script_state = 0;
#define SCRIPT_RUNNING 1
#define SCRIPT_JUST_FINISHED -1
#define SCRIPT_IDLE 0

static int script_stop_requested = 0;

static int script_preview_flag = 0;
static char script_preview[1000] = "";

/* script functions */
#define MAX_SCRIPT_NUM 15
#define FILENAME_SIZE 15
#define SCRIPT_TITLE_SIZE 21

static char script_list[MAX_SCRIPT_NUM][FILENAME_SIZE];
static char script_titles[MAX_SCRIPT_NUM][SCRIPT_TITLE_SIZE];

static int script_selected = 0;
static int script_cnt = 0;

static void script_reset_params();

static char* get_script_path(int script_index)
{
    static char path[50];
    snprintf(path, sizeof(path), "ML/SCRIPTS/%s", script_list[script_index]);
    return path;
}

static void guess_script_title_from_first_line(char* src, char* dst, int maxsize)
{
    while (isspace(*src) || *src == '/' || *src == '*') // skip comments
        src++;

    snprintf(dst, maxsize, "%s", src);
    
    // trim at first newline
    for (char* c = dst; *c; c++)
        if (*c == '\n') { *c = 0; break; }
    
    // skip comment chars at the end of line
    char* last = dst + strlen(dst) - 1;
    while (isspace(*last) || *last == '/' || *last == '*')
        last--;
    last++;
    if (last > dst)
        *last = 0;
}

static void script_parse_header(int script_index)
{
    script_selected = script_index; // side effect (!)

    // clear params submenu (hide all unused stuff)
    script_reset_params();

    // try to read the script 
    static char _buf[1024+128];
    char* buf = UNCACHEABLE(_buf)+64;
    char* fn = get_script_path(script_selected);
    int r = read_file(fn, buf, 1024);
    if (r < 0)
    {
        snprintf(script_titles[script_index], SCRIPT_TITLE_SIZE, "Error");
        return;
    }
    buf[r+1] = 0;

    // get some default title
    guess_script_title_from_first_line(buf, script_titles[script_index], SCRIPT_TITLE_SIZE);
    
    // parse CHDK script header
    script_scan(fn, buf);
    
    // update submenu
    script_update_menu();
}

/* modify from is_valid_cropmark_filename */
static int is_valid_script_filename(char* filename)
{
    int n = strlen(filename);
    if ((n > 2) && (streq(filename + n - 2, ".C") || streq(filename + n - 2, ".c")) && (filename[0] != '.') && (filename[0] != '_'))
        return 1;
    return 0;
}

static void find_scripts(void)
{
    struct fio_file file;
    struct fio_dirent * dirent = FIO_FindFirstEx( "ML/SCRIPTS/", &file );
    if( IS_ERROR(dirent) )
    {
        NotifyBox(2000, "Scripts dir missing" );
        return;
    }
    script_cnt = 0;
    do {
        if (file.mode & ATTR_DIRECTORY) continue; // is a directory
        if (is_valid_script_filename(file.name)) {
            
            snprintf(script_list[script_cnt++], FILENAME_SIZE, "%s", file.name);

            if (script_cnt >= MAX_SCRIPT_NUM)
            {
                NotifyBox(2000, "Too many scripts" );
                break;
            }
        }
    } while( FIO_FindNextEx( dirent, &file ) == 0);
    FIO_FindClose(dirent);
    
    for (int i = 0; i < script_cnt; i++)
        script_parse_header(i);
}

static char* get_script_status_msg()
{
    return 
        script_state == SCRIPT_IDLE ? "idle" :
        script_state == SCRIPT_RUNNING ? (script_stop_requested ? "stopping" : "running") :
        script_state == SCRIPT_JUST_FINISHED ? "finished" : "err";
}

static MENU_UPDATE_FUNC(script_run_display)
{
    if (script_state)
    {
        MENU_SET_NAME(
            "Script %s...", get_script_status_msg()
        );
        MENU_SET_ICON(MNI_RECORD, 0);
    }
    else 
    {
        MENU_SET_NAME("Run script");
    }
}

static void script_copy_window(char* dst, int bufsize, char* src, int line0, int col0, int maxlines, int maxcols)
{
    int l = 0;
    int c = 0;
    char* d_end = dst + bufsize;
    char* s = src;
    char* d = dst;
    for ( ; *s != 0 && d < d_end-1; s++)
    {
        // our character falls within our window? if so, copy it
        int line_condition = (l >= line0 && l < line0 + maxlines);
        int column_condition = (c >= col0 && c < col0 + maxcols) || (*s == '\n');
        if (line_condition && column_condition)
        {
            *d = *s;
            d++;
        }
        else if (c >= col0 + maxcols)
        {
            *(d-1) = *(d-2) = *(d-3) = '.';
        }
        
        // update current position in source string
        if (*s == '\n') { l++; c=0; } else c++;
    }
    *d = 0;
}

static MENU_UPDATE_FUNC(script_print)
{
    if (!script_preview_flag || !entry->selected)
    {
        MENU_SET_NAME("Show script");
        MENU_SET_VALUE("");
        script_preview_flag = 0;
        return;
    }
    
    static int prev_script = -1;

    if (prev_script != script_selected)
    {
        int size;
        char* p = get_script_path(script_selected);
        char* f = (char*)read_entire_file(p, &size);
        if (f)
        {
            script_copy_window(script_preview, sizeof(script_preview), f, 0, 0, 20, 60);
            fio_free(f);
        }
        else
        {
            snprintf(script_preview, sizeof(script_preview), "Could not read '%s'", p);
        }
    }
    prev_script = script_selected;

    bmp_fill(40, 0, 0, 720, 430);
    int fnt = FONT(FONT_MED, COLOR_WHITE, 40);
    big_bmp_printf(fnt, 10, 10, "%s", script_preview);

    info->custom_drawing = CUSTOM_DRAW_THIS_MENU;
}

/*static void script_select(void* priv, int delta)
{
    script_selected = MOD(script_selected + delta, script_cnt);
}*/

static void run_script(const char *script)
{
    extern int ml_started;
    while (!ml_started) msleep(100); // for startup scripts

    script_state = SCRIPT_RUNNING;

    msleep(500);
    
    console_clear();
    console_show();
    console_set_help_text("SET: show/hide");
    console_set_status_text("Script running...");
    
    int exit_code = tcc_compile_and_run(get_script_path(script_selected));
    if (exit_code == 0)
    {
        console_puts(    "Script finished.\n");
    }
    else
    {
        console_printf(  "Script error: %d.\n", exit_code);
        msleep(500);
        gui_stop_menu();
        msleep(500);
        console_show();
    }
    
    // restore some settings to normal, if script changed them
    //~ script_cleanup_af();
    //~ bmp_draw_to_idle(0);
    //~ canon_gui_enable_front_buffer(0);
    beep();
    script_state = SCRIPT_JUST_FINISHED;
    console_set_status_text("Script finished. ");
    msleep(500);
    redraw();
    msleep(500);
    for (int i = 0; i < 100; i++)
    {
        msleep(100);
        if (gui_menu_shown()) break;
        if (get_halfshutter_pressed()) break;
    }
    console_hide();
    script_state = SCRIPT_IDLE;
    console_set_status_text("");
}

static void script_run_fun(void* priv, int delta)
{
    if (script_state) 
    { 
        script_stop_requested = 1;
    }
    else
    {
        script_stop_requested = 0;
        gui_stop_menu();
        task_create("run_script", 0x1c, 0x4000, run_script, 0);
    }
}

/* end script functions */

/**
 * SET: show/hide console (while running, or 10 seconds after finished)
 * 
 */
static unsigned int script_keypress_cbr(unsigned int key)
{
    if (gui_menu_shown()) return 1;
    
    if (script_state == SCRIPT_RUNNING)
    {
        //~ if (handle_picoc_lib_keys(event)==0) return 0;
    }
    
    //~ extern int console_visible;

    if (script_state != SCRIPT_IDLE) // toggle show/hide
    {
        if (key == MODULE_KEY_PRESS_SET && display_idle())
        {
            console_toggle();
            return 0;
        }   
    }
    /*
    if (script_state == SCRIPT_JUST_FINISHED) // after script finished, hide the console on first key press
    {
        if (key == MODULE_KEY_UNPRESS_SET)
        {
            return 0;
        }   
        if (console_visible)
        {
            console_hide();
            return 1;
        }
    }*/
    return 1;
}

static void script_open_submenu(void* priv, int delta)
{
    if (script_state == SCRIPT_IDLE)
    {
        // update the submenu structure on the fly
        static int prev_selected = -1;
        if (prev_selected != script_selected)
        {
            script_parse_header(script_selected);
        }
        prev_selected = script_selected;

        if (delta < 0) // user pressed PLAY
        {
            script_run_fun(priv, delta);
            return;
        }
   
        // now we can display it :)
        menu_open_submenu();
    }
    else
    {
        if (delta < 0) // user pressed PLAY
        {
            script_run_fun(priv, delta);
            return;
        }

        if (script_selected == (int)priv)
        {
            // display only the submenu for the running script, but not the others
            menu_open_submenu();
        }
    }
}

static MENU_UPDATE_FUNC(script_display)
{
    int script_displayed = (int) entry->priv;
    
    if (entry->selected && script_state == SCRIPT_IDLE) 
        script_selected = script_displayed; // change selected script as we scroll thru menu (if not running, of course)

    int displayed_script_is_idle = (script_state == SCRIPT_IDLE) || (script_selected != script_displayed);

    
    if (displayed_script_is_idle)
    {
        MENU_SET_RINFO(
            script_titles[script_displayed]
        );
        MENU_SET_ICON(MNI_SUBMENU, entry->selected && script_displayed == script_selected);
        MENU_SET_ENABLED(0);
        MENU_SET_VALUE("");
    }
    else
    {
        MENU_SET_ICON(MNI_ON, 0);
        MENU_SET_ENABLED(1);
        MENU_SET_VALUE("(%s)", get_script_status_msg());
    }
}

static struct menu_entry tccgui_submenu[] = {
        {
            .name = "Show script",
            .priv = &script_preview_flag,
            .max = 1,
            .icon_type = IT_ACTION,
            .update    = script_print,
            .help = "Display the contents of the selected script.",
        },
        {
            .name = "Run script",
            .update    = script_run_display,
            .select        = script_run_fun,
            .help = "Execute the selected script.",
        },
        {
            .help = "Script parameter #1",
        },
        {
            .help = "Script parameter #2",
        },
        {
            .help = "Script parameter #3",
        },
        {
            .help = "Script parameter #4",
        },
        {
            .help = "Script parameter #5",
        },
        {
            .help = "Script parameter #6",
        },
        {
            .help = "Script parameter #7",
        },
        {
            .help = "Script parameter #8",
        },
        {
            .help = "Script parameter #9",
        },
        MENU_EOL
};

#define MAX_PARAMS (COUNT(tccgui_submenu) - 3)

#define SCRIPT_ENTRY(i) \
        { \
            .name = script_list[i], \
            .priv = (void*)i, \
            .select = script_open_submenu, \
            .select_Q = script_open_submenu, \
            .update = script_display, \
            .icon_type = IT_SUBMENU, \
            .submenu_width = 700, \
            .children = tccgui_submenu, \
            .help  = "Run C scripts with TinyCC, http://bellard.org/tcc/.", \
            .help2 = "PLAY: quick start.", \
        },

static struct menu_entry tccgui_menu[] = {
    SCRIPT_ENTRY(0)
    SCRIPT_ENTRY(1)
    SCRIPT_ENTRY(2)
    SCRIPT_ENTRY(3)
    SCRIPT_ENTRY(4)
    SCRIPT_ENTRY(5)
    SCRIPT_ENTRY(6)
    SCRIPT_ENTRY(7)
    SCRIPT_ENTRY(8)
    SCRIPT_ENTRY(9)
    SCRIPT_ENTRY(10)
    SCRIPT_ENTRY(11)
    SCRIPT_ENTRY(12)
    SCRIPT_ENTRY(13)
    SCRIPT_ENTRY(14)
};

static void script_setup_param(
    int param_index,  // 0-5
    char* param_name, // e.g. "Number of shots"
    int* param_value, // pointer to param value (priv)
    int min_value, 
    int max_value
    )
{
    if (param_index >= MAX_PARAMS) return;
    struct menu_entry * entry = &(tccgui_menu[script_selected].children[2 + param_index]);
    entry->name = param_name;
    entry->priv = param_value;
    entry->min = min_value;
    entry->max = max_value;
    entry->shidden = 0;
}

static void script_reset_params()
{
    for (int i = 0; i < MAX_PARAMS; i++)
    {
        struct menu_entry * entry = &(tccgui_menu[script_selected].children[2 + i]);
        entry->name = 0;
        entry->priv = 0;
        entry->min = 0;
        entry->max = 0;
        entry->icon_type = 0;
        entry->shidden = 1;
    }
}

static void script_setup_title(char* script_title)
{
    snprintf(script_titles[script_selected], SCRIPT_TITLE_SIZE, "%s", script_title);
}

// Queue for PicoC key handling

static struct msg_queue * script_key_mq = 0;

static int script_key_dequeue()
{
    if (!script_key_mq) return -1;
    
    int msg;
    int err = msg_queue_receive(script_key_mq, (struct event**)&msg, 10);
    if (err) return -1;
    return msg;
}

static void script_key_enqueue(int key)
{
    if (!script_key_mq) return;

    msg_queue_post(script_key_mq, key);
}

static void run_startup_script()
{
    for (int i = 0; i < script_cnt; i++)
    {
        if (streq(script_list[i], "AUTORUN.C"))
        {
            script_selected = i;
            script_parse_header(i);
            script_run_fun(0,0);
            return;
        }
    }
}

static unsigned int tccgui_init()
{
    script_key_mq = (struct msg_queue *) msg_queue_create("script_key", 10);
    find_scripts();
    run_startup_script();
    menu_add("Scripts", tccgui_menu, MIN(script_cnt, COUNT(tccgui_menu)));
    return 0;
}

static unsigned int tccgui_deinit()
{
    return 0;
}

MODULE_INFO_START()
    MODULE_INIT(tccgui_init)
    MODULE_DEINIT(tccgui_deinit)
MODULE_INFO_END()

MODULE_CBRS_START()
    //~ MODULE_CBR(CBR_KEYPRESS, script_keypress_cbr, 0)
MODULE_CBRS_END()

#include "chdk-gui_script.c"
back to top