/* * TilEm II * * Copyright (c) 2010-2011 Thibault Duponchelle * Copyright (c) 2010-2011 Benjamin Moody * * 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "gui.h" #include "emucore.h" #include "ti81prg.h" #include "msgbox.h" /**************** Internal link emulation ****************/ /* Open cable */ static int ilp_open(CableHandle* cbl) { TilemCalcEmulator* emu = cbl->priv; tilem_em_lock(emu); if (emu->ilp_active) { fprintf(stderr, "INTERNAL ERROR: cable already opened\n"); tilem_em_unlock(emu); return 1; } emu->ilp_active = TRUE; tilem_linkport_graylink_reset(emu->calc); tilem_em_unlock(emu); return 0; } /* Close cable */ static int ilp_close(CableHandle* cbl) { TilemCalcEmulator* emu = cbl->priv; tilem_em_lock(emu); if (!emu->ilp_active) { fprintf(stderr, "INTERNAL ERROR: cable already closed\n"); tilem_em_unlock(emu); return 1; } emu->ilp_active = FALSE; emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_NONE; tilem_linkport_graylink_reset(emu->calc); tilem_em_unlock(emu); return 0; } /* Reset cable */ static int ilp_reset(CableHandle* cbl) { TilemCalcEmulator* emu = cbl->priv; tilem_em_lock(emu); tilem_linkport_graylink_reset(emu->calc); tilem_em_unlock(emu); return 0; } /* Send data to calc */ static int ilp_send(CableHandle* cbl, uint8_t* data, uint32_t count) { TilemCalcEmulator* emu = cbl->priv; int timeout = cbl->timeout * 100000; tilem_em_lock(emu); while (count > 0) { if (tilem_em_send_byte(emu, data[0], timeout, TRUE)) { tilem_em_unlock(emu); return ERROR_WRITE_TIMEOUT; } data++; count--; } tilem_em_unlock(emu); return 0; } /* cool-down period required after receiving and before sending */ #define COOLDOWN 10000 /* Receive data from calc */ static int ilp_recv(CableHandle* cbl, uint8_t* data, uint32_t count) { TilemCalcEmulator* emu = cbl->priv; int timeout = cbl->timeout * 100000; int value; tilem_em_lock(emu); while (count > 0) { value = tilem_em_get_byte(emu, timeout, TRUE); if (value < 0) { tilem_em_unlock(emu); return ERROR_READ_TIMEOUT; } data[0] = value; data++; count--; } tilem_em_delay(emu, COOLDOWN, TRUE); tilem_em_unlock(emu); return 0; } /* Check if ready */ static int ilp_check(CableHandle* cbl, int* status) { TilemCalcEmulator* emu = cbl->priv; tilem_em_lock(emu); *status = STATUS_NONE; if (emu->calc->linkport.lines) *status |= STATUS_RX; if (emu->calc->linkport.extlines) *status |= STATUS_TX; tilem_em_unlock(emu); return 0; } /* Open a cable */ static CableHandle* internal_link_handle_new(TilemCalcEmulator* emu) { CableHandle* cbl; cbl = ticables_handle_new(CABLE_ILP, PORT_0); if (!cbl) return NULL; cbl->priv = emu; cbl->cable->open = ilp_open; cbl->cable->close = ilp_close; cbl->cable->reset = ilp_reset; cbl->cable->send = ilp_send; cbl->cable->recv = ilp_recv; cbl->cable->check = ilp_check; return cbl; } /**************** Automatic link menu ****************/ /* Run a key (wait, press, wait; release; wait) */ static void run_with_key(TilemCalcEmulator* emu, int key) { tilem_em_delay(emu, 50000, TRUE); tilem_keypad_press_key(emu->calc, key); tilem_em_delay(emu, 50000, TRUE); tilem_keypad_release_key(emu->calc, key); tilem_em_delay(emu, 50000, TRUE); } /* Automatically press key to be in the receive mode (ti82 and ti85) */ static void prepare_for_link_send(TilemCalcEmulator* emu) { tilem_em_lock(emu); tilem_em_wake_up(emu, TRUE); if (emu->calc->hw.model_id == TILEM_CALC_TI82) { run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_GRAPHVAR); run_with_key(emu, TILEM_KEY_RIGHT); run_with_key(emu, TILEM_KEY_ENTER); } else if (emu->calc->hw.model_id == TILEM_CALC_TI85) { run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_GRAPHVAR); run_with_key(emu, TILEM_KEY_WINDOW); } tilem_em_unlock(emu); } /* Automatically press key to be in the send mode (ti82 and ti85) */ static void prepare_for_link_receive(TilemCalcEmulator *emu) { tilem_em_lock(emu); tilem_em_wake_up(emu, TRUE); if (emu->calc->hw.model_id == TILEM_CALC_TI82) { run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_GRAPHVAR); run_with_key(emu, TILEM_KEY_ENTER); tilem_em_delay(emu, 10000000, TRUE); run_with_key(emu, TILEM_KEY_RIGHT); run_with_key(emu, TILEM_KEY_ENTER); } else if (emu->calc->hw.model_id == TILEM_CALC_TI85) { run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_MODE); run_with_key(emu, TILEM_KEY_2ND); run_with_key(emu, TILEM_KEY_GRAPHVAR); run_with_key(emu, TILEM_KEY_YEQU); run_with_key(emu, TILEM_KEY_GRAPH); tilem_em_delay(emu, 10000000, TRUE); run_with_key(emu, TILEM_KEY_ZOOM); tilem_em_delay(emu, 10000000, TRUE); run_with_key(emu, TILEM_KEY_YEQU); } tilem_em_unlock(emu); } /**************** Calc handle ****************/ static GStaticPrivate current_emu_key = G_STATIC_PRIVATE_INIT; /* ticalcs progress bar callback */ static void pbar_do_update() { TilemCalcEmulator *emu = g_static_private_get(¤t_emu_key); CalcUpdate *upd = emu->link_update; gdouble frac; if (upd->max1 > 0 && upd->max2 > 0) frac = ((gdouble) upd->cnt1 / upd->max1 + upd->cnt2) / upd->max2; else if (upd->max1 > 0) frac = ((gdouble) upd->cnt1 / upd->max1); else if (upd->max2 > 0) frac = ((gdouble) upd->cnt2 / upd->max2); else frac = -1.0; tilem_em_set_progress(emu, frac, upd->text); } /* Get the calc model (compatible for ticalcs) */ int get_calc_model(TilemCalc *calc) { return model_to_calcmodel(calc->hw.model_id); } /* Create a calc handle */ void begin_link(TilemCalcEmulator *emu, CableHandle **cbl, CalcHandle **ch, const char *title) { tilem_em_unlock(emu); *cbl = internal_link_handle_new(emu); emu->link_update->max1 = 0; emu->link_update->max2 = 0; emu->link_update->text[0] = 0; emu->link_update->pbar = &pbar_do_update; emu->link_update->label = &pbar_do_update; g_static_private_set(¤t_emu_key, emu, NULL); tilem_em_set_progress_title(emu, title); *ch = ticalcs_handle_new(get_calc_model(emu->calc)); if (!*ch) { g_critical("unsupported calc"); return; } ticalcs_update_set(*ch, emu->link_update); ticalcs_cable_attach(*ch, *cbl); } /* Destroy calc handle */ void end_link(TilemCalcEmulator *emu, CableHandle *cbl, CalcHandle *ch) { tilem_em_set_progress_title(emu, NULL); ticalcs_cable_detach(ch); ticalcs_handle_del(ch); ticables_handle_del(cbl); tilem_em_lock(emu); } /**************** Error messages ****************/ static char * get_tilibs_error(int errcode) { char *p = NULL; if (!ticalcs_error_get(errcode, &p) || !ticables_error_get(errcode, &p) || !tifiles_error_get(errcode, &p)) return p; else return g_strdup_printf("Unknown error (%d)", errcode); } static char * get_ti81_error(int errcode) { switch (errcode) { case TI81_ERR_FILE_IO: return g_strdup("File I/O error"); case TI81_ERR_INVALID_FILE: return g_strdup("Not a valid PRG file"); case TI81_ERR_MEMORY: return g_strdup("The calculator does not have enough free memory" " to load the program."); case TI81_ERR_SLOTS_FULL: return g_strdup("All calculator program slots are in use. " " You must delete an existing program before" " loading a new program."); case TI81_ERR_BUSY: return g_strdup("The calculator is currently busy. Please" " exit to the home screen before loading" " programs."); default: return g_strdup_printf("Unknown error code (%d)", errcode); } } void show_error(TilemCalcEmulator *emu, const char *title, const char *message) { if (emu->ewin) messagebox11(emu->ewin->window, GTK_MESSAGE_ERROR, "%s", title, "%s", message); else g_printerr("\n=== %s ===\n%s\n\n", title, message); } /**************** Sending files ****************/ /* Send a file to TI-81 */ static gboolean send_file_ti81(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf) { TI81Program *prgm = NULL; FILE *f; int errnum; sf->error_message = NULL; f = g_fopen(sf->filename, "rb"); if (!f) { sf->error_message = g_strdup_printf ("Failed to open %s for reading: %s", sf->display_name, g_strerror(errno)); return FALSE; } if (ti81_read_prg_file(f, &prgm)) { sf->error_message = g_strdup_printf ("The file %s is not a valid TI-81 program file.", sf->display_name); fclose(f); return FALSE; } fclose(f); tilem_em_wake_up(emu, TRUE); prgm->info.slot = sf->slot; errnum = ti81_load_program(emu->calc, prgm); ti81_program_free(prgm); if (errnum && !emu->task_abort) sf->error_message = get_ti81_error(errnum); return (errnum == 0); } /* Get application name */ static gboolean get_app_name(const FlashContent *flashc, char *name) { int i; const unsigned char *data; unsigned int type, length; if (flashc->num_pages < 1 || flashc->pages[0]->size < 6 || flashc->pages[0]->data[0] != 0x80 || flashc->pages[0]->data[1] != 0x0f) return FALSE; i = 6; data = flashc->pages[0]->data; while (i < flashc->pages[0]->size && i < 128) { type = (data[i] << 8 | (data[i + 1] & 0xf0)); length = data[i + 1] & 0x0f; i += 2; if (length == 0x0d) { length = data[i]; i++; } else if (length == 0x0e) { length = (data[i] << 8 | data[i + 1]); i += 2; } else if (length == 0x0f) { return FALSE; } if (type == 0x8070) return FALSE; if (type == 0x8040) { memcpy(name, data + i, length > 8 ? 8 : length); return TRUE; } } return FALSE; } /* Try to delete an existing Flash app before we send a replacement */ static void try_delete_app(CalcHandle *ch, const FlashContent *flashc) { VarRequest vr; /* TI-73 does not support remote deletion */ if (ch->model == CALC_TI73) return; memset(&vr, 0, sizeof(VarRequest)); if (!get_app_name(flashc, vr.name)) return; /* Why does this use type 0x14 and not 0x24? I don't know. */ vr.type = 0x14; ticalcs_calc_del_var(ch, &vr); /* if an error occurs, ignore it */ } /* Send a file using ticalcs2 */ static gboolean send_file_linkport(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf) { CalcModel model; FileClass cls; CableHandle *cbl; CalcHandle *ch; FileContent *filec; BackupContent *backupc; FlashContent *flashc; TigContent *tigc; CalcMode mode; int e; char *desc; model = get_calc_model(emu->calc); cls = tifiles_file_get_class(sf->filename); desc = g_strdup_printf("Sending %s", sf->display_name); /* Read input file */ switch (cls) { case TIFILE_SINGLE: case TIFILE_GROUP: case TIFILE_REGULAR: filec = tifiles_content_create_regular(model); e = tifiles_file_read_regular(sf->filename, filec); if (!e) { begin_link(emu, &cbl, &ch, desc); if (sf->first) prepare_for_link_send(emu); mode = (sf->last ? MODE_SEND_LAST_VAR : MODE_NORMAL); e = ticalcs_calc_send_var(ch, mode, filec); end_link(emu, cbl, ch); } tifiles_content_delete_regular(filec); break; case TIFILE_BACKUP: backupc = tifiles_content_create_backup(model); e = tifiles_file_read_backup(sf->filename, backupc); if (!e) { begin_link(emu, &cbl, &ch, desc); prepare_for_link_send(emu); e = ticalcs_calc_send_backup(ch, backupc); end_link(emu, cbl, ch); } tifiles_content_delete_backup(backupc); break; case TIFILE_FLASH: case TIFILE_OS: case TIFILE_APP: flashc = tifiles_content_create_flash(model); e = tifiles_file_read_flash(sf->filename, flashc); if (!e) { begin_link(emu, &cbl, &ch, desc); ticables_options_set_timeout(cbl, 30 * 10); prepare_for_link_send(emu); if (tifiles_file_is_os(sf->filename)) e = ticalcs_calc_send_os(ch, flashc); else if (tifiles_file_is_app(sf->filename)) { try_delete_app(ch, flashc); e = ticalcs_calc_send_app(ch, flashc); } else e = ticalcs_calc_send_cert(ch, flashc); end_link(emu, cbl, ch); } tifiles_content_delete_flash(flashc); break; case TIFILE_TIGROUP: tigc = tifiles_content_create_tigroup(model, 0); e = tifiles_file_read_tigroup(sf->filename, tigc); if (!e) { begin_link(emu, &cbl, &ch, desc); prepare_for_link_send(emu); e = ticalcs_calc_send_tigroup(ch, tigc, TIG_ALL); end_link(emu, cbl, ch); } tifiles_content_delete_tigroup(tigc); break; default: g_free(desc); sf->error_message = g_strdup_printf ("The file %s is not a valid program or" " variable file.", sf->display_name); return FALSE; } g_free(desc); if (e && !emu->task_abort) sf->error_message = get_tilibs_error(e); return (e == 0); } gboolean send_file_main(TilemCalcEmulator *emu, gpointer data) { struct TilemSendFileInfo *sf = data; /*emu->ilp.finished_cond = g_cond_new(); */ if (emu->calc->hw.model_id == TILEM_CALC_TI81) return send_file_ti81(emu, sf); else return send_file_linkport(emu, sf); } static void send_file_finished(TilemCalcEmulator *emu, gpointer data, gboolean cancelled) { struct TilemSendFileInfo *sf = data; if (sf->error_message && !cancelled) show_error(emu, "Unable to send file", sf->error_message); g_free(sf->filename); g_free(sf->display_name); g_free(sf->error_message); g_slice_free(struct TilemSendFileInfo, sf); /*g_cond_broadcast(emu->ilp.finished_cond);*/ } void tilem_link_send_file(TilemCalcEmulator *emu, const char *filename, int slot, gboolean first, gboolean last) { struct TilemSendFileInfo *sf; sf = g_slice_new0(struct TilemSendFileInfo); sf->filename = g_strdup(filename); sf->display_name = g_filename_display_basename(filename); sf->slot = slot; sf->first = first; sf->last = last; tilem_calc_emulator_begin(emu, &send_file_main, &send_file_finished, sf); } /**************** Get directory listing ****************/ /* Make a copy of a TilemVarEntry */ TilemVarEntry *tilem_var_entry_copy(const TilemVarEntry *tve) { TilemVarEntry *nve; g_return_val_if_fail(tve != NULL, NULL); nve = g_slice_new(TilemVarEntry); *nve = *tve; if (tve->ve) { nve->ve = g_slice_new(VarEntry); *nve->ve = *tve->ve; nve->ve->data = g_memdup(tve->ve->data, tve->ve->size); } if (tve->name_str) nve->name_str = g_strdup(tve->name_str); if (tve->type_str) nve->type_str = g_strdup(tve->type_str); if (tve->slot_str) nve->slot_str = g_strdup(tve->slot_str); if (tve->file_ext) nve->file_ext = g_strdup(tve->file_ext); if (tve->filetype_desc) nve->filetype_desc = g_strdup(tve->filetype_desc); return nve; } /* Free a TilemVarEntry */ void tilem_var_entry_free(TilemVarEntry *tve) { g_return_if_fail(tve != NULL); if (tve->ve) { g_free(tve->ve->data); g_slice_free(VarEntry, tve->ve); } g_free(tve->name_str); g_free(tve->type_str); g_free(tve->slot_str); g_free(tve->file_ext); g_free(tve->filetype_desc); g_slice_free(TilemVarEntry, tve); } struct dirlistinfo { GSList *list; char *error_message; gboolean aborted; gboolean no_gui; }; /* Convert tifiles VarEntry into a TilemVarEntry */ static TilemVarEntry *convert_ve(TilemCalcEmulator *emu, VarEntry *ve, gboolean is_flash) { TilemVarEntry *tve = g_slice_new0(TilemVarEntry); CalcModel tfmodel = get_calc_model(emu->calc); const char *model_str; const char *type_str; const char *fext; tve->model = emu->calc->hw.model_id; tve->ve = g_slice_new(VarEntry); *tve->ve = *ve; if (ve->data) tve->ve->data = g_memdup(ve->data, ve->size); tve->size = ve->size; tve->archived = (ve->attr & ATTRB_ARCHIVED ? TRUE : FALSE); tve->can_group = TRUE; tve->name_str = ticonv_varname_to_utf8(tfmodel, ve->name, ve->type); g_strchomp(tve->name_str); tve->type_str = g_strdup(tifiles_vartype2string(tfmodel, ve->type)); fext = tifiles_vartype2fext(tfmodel, ve->type); tve->file_ext = g_ascii_strdown(fext, -1); /* FIXME: the filetype_desc string is used as a description in the file chooser. It should be written in the same style as other such strings (e.g., "TI-83 Plus programs" rather than "TI83+ Program".) But this is better than nothing. */ model_str = tifiles_model_to_string(tfmodel); type_str = tifiles_vartype2type(tfmodel, ve->type); tve->filetype_desc = g_strdup_printf("%s %s", model_str, type_str); tve->can_group = !is_flash; return tve; } /* Convert a complete directory listing */ static void convert_dir_list(TilemCalcEmulator *emu, GNode *root, gboolean is_flash, GSList **list) { GNode *dir, *var; VarEntry *ve; TilemVarEntry *tve; if (!root) return; for (dir = root->children; dir; dir = dir->next) { for (var = dir->children; var; var = var->next) { ve = var->data; tve = convert_ve(emu, ve, is_flash); *list = g_slist_prepend(*list, tve); } } } /* Request directory listing using ticalcs */ static gboolean get_dirlist_silent(TilemCalcEmulator *emu, struct dirlistinfo *dl) { CableHandle *cbl; CalcHandle *ch; GNode *vars = NULL, *apps = NULL; GSList *list = NULL; int e = 0; begin_link(emu, &cbl, &ch, "Reading variable list"); prepare_for_link_receive(emu); if (ticalcs_calc_features(ch) & OPS_DIRLIST) { e = ticalcs_calc_get_dirlist(ch, &vars, &apps); if (!e) { convert_dir_list(emu, vars, FALSE, &list); convert_dir_list(emu, apps, TRUE, &list); } ticalcs_dirlist_destroy(&vars); ticalcs_dirlist_destroy(&apps); } end_link(emu, cbl, ch); dl->list = g_slist_reverse(list); dl->aborted = emu->task_abort; if (e && !emu->task_abort) dl->error_message = get_tilibs_error(e); return (e == 0); } /* Transfer variables non-silently using ticalcs */ static gboolean get_dirlist_nonsilent(TilemCalcEmulator *emu, struct dirlistinfo *dl) { CableHandle *cbl; CalcHandle *ch; FileContent *fc; VarEntry *head_entry; TilemVarEntry *tve; GSList *list = NULL; int e, i; begin_link(emu, &cbl, &ch, "Receiving variables"); prepare_for_link_receive(emu); fc = tifiles_content_create_regular(ch->model); e = ticalcs_calc_recv_var_ns(ch, MODE_BACKUP, fc, &head_entry); if (!e) { for (i = 0; i < fc->num_entries; i++) { tve = convert_ve(emu, fc->entries[i], FALSE); list = g_slist_prepend(list, tve); } } if (head_entry) tifiles_ve_delete(head_entry); tifiles_content_delete_regular(fc); end_link(emu, cbl, ch); dl->list = g_slist_reverse(list); dl->aborted = emu->task_abort; if (e && !emu->task_abort) dl->error_message = get_tilibs_error(e); return (e == 0); } /* Get TI-81 directory listing */ static gboolean get_dirlist_ti81(TilemCalcEmulator *emu, struct dirlistinfo *dl) { int i, slot; TI81ProgInfo info; GSList *list = NULL; TilemVarEntry *tve; int e; tilem_em_wake_up(emu, TRUE); for (i = 0; i <= TI81_SLOT_MAX; i++) { /* put Prgm0 after Prgm9, the way it appears in the menu */ if (i < 9) slot = i + 1; else if (i == 9) slot = 0; else slot = i; if ((e = ti81_get_program_info(emu->calc, slot, &info))) break; if (info.size == 0) continue; tve = g_slice_new0(TilemVarEntry); tve->model = TILEM_CALC_TI81; tve->slot = info.slot; tve->name_str = ti81_program_name_to_string(info.name); tve->slot_str = ti81_program_slot_to_string(info.slot); tve->file_ext = g_strdup("prg"); tve->filetype_desc = g_strdup("TI-81 programs"); tve->size = info.size; tve->archived = FALSE; tve->can_group = FALSE; list = g_slist_prepend(list, tve); } dl->list = g_slist_reverse(list); if (e && !emu->task_abort) dl->error_message = get_ti81_error(e); return (e == 0); } static gboolean get_dirlist_main(TilemCalcEmulator *emu, gpointer data) { switch (emu->calc->hw.model_id) { case TILEM_CALC_TI81: return get_dirlist_ti81(emu, data); case TILEM_CALC_TI82: case TILEM_CALC_TI85: return get_dirlist_nonsilent(emu, data); default: return get_dirlist_silent(emu, data); } } static void get_dirlist_finished(TilemCalcEmulator *emu, gpointer data, gboolean cancelled) { GSList *l; struct dirlistinfo *dl = data; if (dl->error_message && !cancelled) show_error(emu, "Unable to receive variable list", dl->error_message); else if (!cancelled && !dl->aborted && emu->ewin && !dl->no_gui) { if (!emu->rcvdlg) emu->rcvdlg = tilem_receive_dialog_new(emu); tilem_receive_dialog_update(emu->rcvdlg, dl->list); dl->list = NULL; } if (!dl->no_gui && emu->rcvdlg) emu->rcvdlg->refresh_pending = FALSE; for (l = dl->list; l; l = l->next) tilem_var_entry_free(l->data); g_slist_free(dl->list); g_slice_free(struct dirlistinfo, dl); } void tilem_link_get_dirlist(TilemCalcEmulator *emu) { struct dirlistinfo *dl = g_slice_new0(struct dirlistinfo); tilem_calc_emulator_begin(emu, &get_dirlist_main, &get_dirlist_finished, dl); } /**************** Receiving files ****************/ static gboolean write_output(FileContent **vars, FlashContent **apps, const char *filename, gboolean output_tig, char **error_message) { FileContent *group = NULL; TigContent *tig = NULL; int e, nvars, napps; for (nvars = 0; vars && vars[nvars]; nvars++) ; for (napps = 0; apps && apps[napps]; napps++) ; g_return_val_if_fail(nvars > 0 || napps > 0, FALSE); if (output_tig) { e = tifiles_tigroup_contents(vars, apps, &tig); if (!e) e = tifiles_file_write_tigroup(filename, tig); } else if (nvars > 1 && napps == 0) { e = tifiles_group_contents(vars, &group); if (!e) e = tifiles_file_write_regular(filename, group, NULL); } else if (nvars == 0 && napps == 1) { e = tifiles_file_write_flash(filename, apps[0]); } else if (nvars == 1 && napps == 0) { e = tifiles_file_write_regular(filename, vars[0], NULL); } else { *error_message = g_strdup ("Applications cannot be saved in an XXg group" " file. Try using TIG format or saving apps" " individually."); return FALSE; } if (e) *error_message = get_tilibs_error(e); if (tig) tifiles_content_delete_tigroup(tig); if (group) tifiles_content_delete_regular(group); return (e == 0); } static gboolean receive_files_silent(TilemCalcEmulator* emu, struct TilemReceiveFileInfo *rf) { CableHandle *cbl; CalcHandle *ch; FileContent **vars, *filec; FlashContent **apps, *flashc; GSList *l; TilemVarEntry *tve; int e, i, nvars, napps; g_return_val_if_fail(rf->entries != NULL, FALSE); i = g_slist_length(rf->entries); vars = g_new0(FileContent *, i + 1); apps = g_new0(FlashContent *, i + 1); nvars = napps = 0; begin_link(emu, &cbl, &ch, "Receiving variables"); for (l = rf->entries; l; l = l->next) { tve = l->data; if (tve->ve->type == tifiles_flash_type(ch->model)) { flashc = tifiles_content_create_flash(ch->model); e = ticalcs_calc_recv_app(ch, flashc, tve->ve); apps[napps++] = flashc; } else { filec = tifiles_content_create_regular(ch->model); e = ticalcs_calc_recv_var(ch, MODE_NORMAL, filec, tve->ve); vars[nvars++] = filec; } if (e) break; } if (e && !emu->task_abort) rf->error_message = get_tilibs_error(e); end_link(emu, cbl, ch); if (!e) e = !write_output(vars, apps, rf->destination, rf->output_tig, &rf->error_message); for (i = 0; i < nvars; i++) tifiles_content_delete_regular(vars[i]); for (i = 0; i < napps; i++) tifiles_content_delete_flash(apps[i]); return (e == 0); } static gboolean receive_files_ti81(TilemCalcEmulator* emu, struct TilemReceiveFileInfo *rf) { TilemVarEntry *tve; TI81Program *prgm = NULL; int e; FILE *f; char *dname; g_return_val_if_fail(rf->entries != NULL, FALSE); if (rf->entries->next) { rf->error_message = g_strdup ("TI-81 programs cannot be saved in a group file." " Try saving programs individually."); return FALSE; } tve = rf->entries->data; e = ti81_get_program(emu->calc, tve->slot, &prgm); if (e) { rf->error_message = get_ti81_error(e); return FALSE; } f = g_fopen(rf->destination, "wb"); if (!f) { e = errno; dname = g_filename_display_basename(rf->destination); rf->error_message = g_strdup_printf ("Failed to open %s for writing: %s", dname, g_strerror(e)); g_free(dname); ti81_program_free(prgm); return FALSE; } e = ti81_write_prg_file(f, prgm); if (fclose(f) || e) { e = errno; dname = g_filename_display_basename(rf->destination); rf->error_message = g_strdup_printf ("Error writing %s: %s", dname, g_strerror(e)); g_free(dname); ti81_program_free(prgm); return FALSE; } ti81_program_free(prgm); return TRUE; } static gboolean receive_files_nonsilent(TilemCalcEmulator *emu, struct TilemReceiveFileInfo *rf) { const TilemVarEntry *tve; FileContent **vars, *fc; int i, nvars; GSList *l; gboolean status; nvars = g_slist_length(rf->entries); vars = g_new0(FileContent *, nvars + 1); i = 0; for (l = rf->entries; l; l = l->next) { tve = l->data; g_return_val_if_fail(tve->ve != NULL, FALSE); g_return_val_if_fail(tve->ve->data != NULL, FALSE); /* avoid copying variable data */ fc = tifiles_content_create_regular(get_calc_model(emu->calc)); fc->entries = g_new(VarEntry *, 1); fc->num_entries = 1; fc->entries[0] = tve->ve; vars[i++] = fc; } status = write_output(vars, NULL, rf->destination, rf->output_tig, &rf->error_message); for (i = 0; i < nvars; i++) { if (vars[i]) { vars[i]->num_entries = 0; g_free(vars[i]->entries); vars[i]->entries = NULL; tifiles_content_delete_regular(vars[i]); } } g_free(vars); return status; } static gboolean receive_files_main(TilemCalcEmulator *emu, gpointer data) { struct TilemReceiveFileInfo *rf = data; TilemVarEntry *tve; g_return_val_if_fail(rf->entries != NULL, FALSE); tve = rf->entries->data; if (emu->calc->hw.model_id == TILEM_CALC_TI81) return receive_files_ti81(emu, rf); else if (tve->ve && tve->ve->data) return receive_files_nonsilent(emu, rf); else return receive_files_silent(emu, rf); } static void receive_files_finished(TilemCalcEmulator *emu, gpointer data, gboolean cancelled) { struct TilemReceiveFileInfo *rf = data; GSList *l; if (rf->error_message && !cancelled) show_error(emu, "Unable to save file", rf->error_message); g_free(rf->destination); g_free(rf->error_message); for (l = rf->entries; l; l = l->next) tilem_var_entry_free(l->data); g_slist_free(rf->entries); g_slice_free(struct TilemReceiveFileInfo, rf); } void tilem_link_receive_group(TilemCalcEmulator *emu, GSList *entries, const char *destination) { struct TilemReceiveFileInfo *rf; GSList *l; TilemVarEntry *tve; const char *p; gboolean output_tig = FALSE; g_return_if_fail(emu != NULL); g_return_if_fail(emu->calc != NULL); g_return_if_fail(entries != NULL); g_return_if_fail(destination != NULL); for (l = entries; l; l = l->next) { tve = l->data; g_return_if_fail(emu->calc->hw.model_id == tve->model); } p = strrchr(destination, '.'); if (p && (!g_ascii_strcasecmp(p, ".tig") || !g_ascii_strcasecmp(p, ".zip"))) output_tig = TRUE; rf = g_slice_new0(struct TilemReceiveFileInfo); rf->destination = g_strdup(destination); rf->output_tig = output_tig; tve = entries->data; if (tve->ve && tve->ve->data) { /* non-silent: we already have the data; save the file right now */ rf->entries = entries; receive_files_nonsilent(emu, rf); rf->entries = NULL; receive_files_finished(emu, rf, FALSE); } else { /* silent: retrieve and save data in background */ for (l = entries; l; l = l->next) { tve = tilem_var_entry_copy(l->data); rf->entries = g_slist_prepend(rf->entries, tve); } rf->entries = g_slist_reverse(rf->entries); tilem_calc_emulator_begin(emu, &receive_files_main, &receive_files_finished, rf); } } void tilem_link_receive_file(TilemCalcEmulator *emu, const TilemVarEntry *varentry, const char* destination) { GSList *l; l = g_slist_prepend(NULL, (gpointer) varentry); tilem_link_receive_group(emu, l, destination); g_slist_free(l); } /**************** Receive matching files ****************/ struct recmatchinfo { char *pattern; char *destdir; struct dirlistinfo *dl; struct TilemReceiveFileInfo *rf; }; static gboolean receive_matching_main(TilemCalcEmulator *emu, gpointer data) { struct recmatchinfo *rm = data; GSList *l, *selected = NULL; TilemVarEntry *tve; char *defname, *defname_r, *defname_l; GPatternSpec *pat; gboolean status = TRUE; if (!get_dirlist_main(emu, rm->dl)) return FALSE; /* Find variables that match the pattern */ pat = g_pattern_spec_new(rm->pattern); for (l = rm->dl->list; l; l = l->next) { tve = l->data; defname = get_default_filename(tve); defname_r = g_utf8_normalize(defname, -1, G_NORMALIZE_NFKD); if (g_pattern_match(pat, strlen(defname_r), defname_r, NULL)) selected = g_slist_prepend(selected, tve); g_free(defname); g_free(defname_r); } g_pattern_spec_free(pat); if (!selected) { rm->rf->error_message = g_strdup_printf ("Variable %s not found", rm->pattern); return FALSE; } /* Receive variables */ selected = g_slist_reverse(selected); for (l = selected; l; l = l->next) { tve = l->data; g_free(rm->rf->destination); defname = get_default_filename(tve); defname_l = utf8_to_filename(defname); rm->rf->destination = g_build_filename(rm->destdir, defname_l, NULL); g_free(defname); g_free(defname_l); rm->rf->entries = g_slist_prepend(NULL, tve); status = receive_files_main(emu, rm->rf); g_slist_free(rm->rf->entries); rm->rf->entries = NULL; if (!status) break; } g_slist_free(selected); return status; } static void receive_matching_finished(TilemCalcEmulator *emu, gpointer data, gboolean cancelled) { struct recmatchinfo *rm = data; get_dirlist_finished(emu, rm->dl, cancelled); receive_files_finished(emu, rm->rf, cancelled); g_free(rm->pattern); g_free(rm->destdir); g_slice_free(struct recmatchinfo, rm); } /* Receive variables with names matching a pattern. PATTERN is a glob-like pattern in UTF-8. Files will be written out to DESTDIR. */ void tilem_link_receive_matching(TilemCalcEmulator *emu, const char *pattern, const char *destdir) { struct recmatchinfo *rm; g_return_if_fail(emu != NULL); g_return_if_fail(pattern != NULL); g_return_if_fail(destdir != NULL); rm = g_slice_new0(struct recmatchinfo); rm->pattern = g_utf8_normalize(pattern, -1, G_NORMALIZE_NFKD); rm->destdir = g_strdup(destdir); rm->dl = g_slice_new0(struct dirlistinfo); rm->dl->no_gui = TRUE; rm->rf = g_slice_new0(struct TilemReceiveFileInfo); tilem_calc_emulator_begin(emu, &receive_matching_main, &receive_matching_finished, rm); }