ti83-sdk/tool/tilem-src/gui/link.c

1302 lines
32 KiB
C
Raw Normal View History

2023-10-08 16:33:37 +00:00
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <ticalcs.h>
#include <ticonv.h>
#include <string.h>
#include <tilem.h>
#include <scancodes.h>
#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(&current_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(&current_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);
}