// references: // https://api.libssh.org/stable/libssh_tutorial.html // https://api.libssh.org/stable/libssh_tutor_guided_tour.html // https://plplot.sourceforge.net/documentation.php // https://plplot.sourceforge.net/examples.php // plplot example 0 and ext-cairo-test.c // https://docs.gtk.org/gtk4/class.DrawingArea.html // https://www.gtk.org/docs/getting-started/hello-world/ // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/print-editor/print-editor.c // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c #include #include #include #include // verify_knownhost() #include #include #include // open() // plplot #include #include #include //#include #include #include #include #include "list.h" #include //uint32_t // use double for PLFLT #define PL_DOUBLE // max wire unsigned number is 65535 // max wire signed number maybe is -32768 to 32767 // voltage max format 6 bytes ,5.999 // temp max format 6 bytes ,-49.9 // timestamp max 10 bytes 1745742182 // 1 timestamp, 96 voltage, 32 temp, 1 \n // 10+96*6+32*6+1=779 bytes #define ENTRY_SIZE 779 // 16 KiB == 16384 bytes // 16384/ENTRY_SIZE == 21 // 21*ENTRY_SIZE == 16359 // https://api.libssh.org/stable/libssh_tutor_sftp.html #define MAX_XFER_BUF_SIZE 16384/ENTRY_SIZE*ENTRY_SIZE const char *checkbutton_names[]={ "0x630_BMS_Cell_4_Voltage", "0x630_BMS_Cell_3_Voltage", "0x630_BMS_Cell_2_Voltage", "0x630_BMS_Cell_1_Voltage", "0x631_BMS_Cell_8_Voltage", "0x631_BMS_Cell_7_Voltage", "0x631_BMS_Cell_6_Voltage", "0x631_BMS_Cell_5_Voltage", "0x632_BMS_Cell_12_Voltage", "0x632_BMS_Cell_11_Voltage", "0x632_BMS_Cell_10_Voltage", "0x632_BMS_Cell_9_Voltage", "0x633_BMS_Cell_4_Voltage", "0x633_BMS_Cell_3_Voltage", "0x633_BMS_Cell_2_Voltage", "0x633_BMS_Cell_1_Voltage", "0x634_BMS_Cell_8_Voltage", "0x634_BMS_Cell_7_Voltage", "0x634_BMS_Cell_6_Voltage", "0x634_BMS_Cell_5_Voltage", "0x635_BMS_Cell_12_Voltage", "0x635_BMS_Cell_11_Voltage", "0x635_BMS_Cell_10_Voltage", "0x635_BMS_Cell_9_Voltage", "0x636_BMS_Cell_4_Voltage", "0x636_BMS_Cell_3_Voltage", "0x636_BMS_Cell_2_Voltage", "0x636_BMS_Cell_1_Voltage", "0x637_BMS_Cell_8_Voltage", "0x637_BMS_Cell_7_Voltage", "0x637_BMS_Cell_6_Voltage", "0x637_BMS_Cell_5_Voltage", "0x638_BMS_Cell_12_Voltage", "0x638_BMS_Cell_11_Voltage", "0x638_BMS_Cell_10_Voltage", "0x638_BMS_Cell_9_Voltage", "0x639_BMS_Cell_4_Voltage", "0x639_BMS_Cell_3_Voltage", "0x639_BMS_Cell_2_Voltage", "0x639_BMS_Cell_1_Voltage", "0x63a_BMS_Cell_8_Voltage", "0x63a_BMS_Cell_7_Voltage", "0x63a_BMS_Cell_6_Voltage", "0x63a_BMS_Cell_5_Voltage", "0x63b_BMS_Cell_12_Voltage", "0x63b_BMS_Cell_11_Voltage", "0x63b_BMS_Cell_10_Voltage", "0x63b_BMS_Cell_9_Voltage", "0x63c_BMS_Cell_4_Voltage", "0x63c_BMS_Cell_3_Voltage", "0x63c_BMS_Cell_2_Voltage", "0x63c_BMS_Cell_1_Voltage", "0x63d_BMS_Cell_8_Voltage", "0x63d_BMS_Cell_7_Voltage", "0x63d_BMS_Cell_6_Voltage", "0x63d_BMS_Cell_5_Voltage", "0x63e_BMS_Cell_12_Voltage", "0x63e_BMS_Cell_11_Voltage", "0x63e_BMS_Cell_10_Voltage", "0x63e_BMS_Cell_9_Voltage", "0x63f_BMS_Cell_4_Voltage", "0x63f_BMS_Cell_3_Voltage", "0x63f_BMS_Cell_2_Voltage", "0x63f_BMS_Cell_1_Voltage", "0x640_BMS_Cell_8_Voltage", "0x640_BMS_Cell_7_Voltage", "0x640_BMS_Cell_6_Voltage", "0x640_BMS_Cell_5_Voltage", "0x641_BMS_Cell_12_Voltage", "0x641_BMS_Cell_11_Voltage", "0x641_BMS_Cell_10_Voltage", "0x641_BMS_Cell_9_Voltage", "0x642_BMS_Cell_4_Voltage", "0x642_BMS_Cell_3_Voltage", "0x642_BMS_Cell_2_Voltage", "0x642_BMS_Cell_1_Voltage", "0x643_BMS_Cell_8_Voltage", "0x643_BMS_Cell_7_Voltage", "0x643_BMS_Cell_6_Voltage", "0x643_BMS_Cell_5_Voltage", "0x644_BMS_Cell_12_Voltage", "0x644_BMS_Cell_11_Voltage", "0x644_BMS_Cell_10_Voltage", "0x644_BMS_Cell_9_Voltage", "0x645_BMS_Cell_4_Voltage", "0x645_BMS_Cell_3_Voltage", "0x645_BMS_Cell_2_Voltage", "0x645_BMS_Cell_1_Voltage", "0x646_BMS_Cell_8_Voltage", "0x646_BMS_Cell_7_Voltage", "0x646_BMS_Cell_6_Voltage", "0x646_BMS_Cell_5_Voltage", "0x647_BMS_Cell_12_Voltage", "0x647_BMS_Cell_11_Voltage", "0x647_BMS_Cell_10_Voltage", "0x647_BMS_Cell_9_Voltage", "0x680_BMS_Section_4_Temp", "0x680_BMS_Section_3_Temp", "0x680_BMS_Section_2_Temp", "0x680_BMS_Section_1_Temp", "0x683_BMS_Section_4_Temp", "0x683_BMS_Section_3_Temp", "0x683_BMS_Section_2_Temp", "0x683_BMS_Section_1_Temp", "0x686_BMS_Section_4_Temp", "0x686_BMS_Section_3_Temp", "0x686_BMS_Section_2_Temp", "0x686_BMS_Section_1_Temp", "0x689_BMS_Section_4_Temp", "0x689_BMS_Section_3_Temp", "0x689_BMS_Section_2_Temp", "0x689_BMS_Section_1_Temp", "0x68c_BMS_Section_4_Temp", "0x68c_BMS_Section_3_Temp", "0x68c_BMS_Section_2_Temp", "0x68c_BMS_Section_1_Temp", "0x68f_BMS_Section_4_Temp", "0x68f_BMS_Section_3_Temp", "0x68f_BMS_Section_2_Temp", "0x68f_BMS_Section_1_Temp", "0x692_BMS_Section_4_Temp", "0x692_BMS_Section_3_Temp", "0x692_BMS_Section_2_Temp", "0x692_BMS_Section_1_Temp", "0x695_BMS_Section_4_Temp", "0x695_BMS_Section_3_Temp", "0x695_BMS_Section_2_Temp", "0x695_BMS_Section_1_Temp" }; typedef struct { ssh_session session; sftp_session sftp; List cans; GtkWidget *area; GtkWidget *checkbutton[VOLTLEN+TEMPLEN]; GtkWidget *volt_label; GtkWidget *temp_label; GtkWidget *live_toggle; GtkWidget *from_entry; GtkWidget *to_entry; // .csv file already read offset uint32_t offset; uint32_t from_time; uint32_t to_time; bool from_time_entered; }DATA; static gboolean sftp_read_sync(gpointer user_data) //int sftp_read_sync(ssh_session session, sftp_session sftp) { //printf("sftp_read_sync begin\n"); DATA *data=user_data; int access_type; sftp_file file; char buffer[MAX_XFER_BUF_SIZE]; int nbytes, rc; access_type = O_RDONLY; // This is to represent a loop over time // Let's try a random walk process file = sftp_open(data->sftp, sftp_expand_path(data->sftp,"~/.local/share/mycan.csv"), access_type, 0); if (file == NULL) { fprintf(stderr, "Can't open file for reading: %s\n", ssh_get_error(data->session)); //return SSH_ERROR; return G_SOURCE_REMOVE; } if(data->offset != 0) sftp_seek(file,data->offset); for(;;) { nbytes = sftp_read(file, buffer, sizeof(buffer)); //printf("nbytes: %d\n",nbytes); //printf("%d\n",MAX_XFER_BUF_SIZE); if (nbytes == 0) { break; // EOF } else if (nbytes < 0) { fprintf(stderr, "Error while reading file: %s\n", ssh_get_error(data->session)); sftp_close(file); //return SSH_ERROR; return G_SOURCE_REMOVE; } else if ((nbytes%ENTRY_SIZE) != 0) { fprintf(stderr, "sftp read nbytes not a multiple of %d\n",ENTRY_SIZE); sftp_close(file); //return SSH_ERROR; return G_SOURCE_REMOVE; } // TODO: check strtok return NULL or not //printf("before strtok\n"); for(int i=0;i<(nbytes/ENTRY_SIZE);i++) { Item temp; // TODO: pass endptr and check it and other things to be safe, see https://stackoverflow.com/questions/34206446 if(i) temp.t=strtoul(strtok(NULL,","),NULL,10); else temp.t=strtoul(strtok(buffer,","),NULL,10); for(int j=0;jcans))==false) { fprintf(stderr,"Problem allocating memory\n"); break; } //printf("after AddItem\n"); } //printf("after strtok\n"); data->offset+=nbytes; } rc = sftp_close(file); if (rc != SSH_OK) { fprintf(stderr, "Can't close the read file: %s\n", ssh_get_error(data->session)); //return rc; return G_SOURCE_REMOVE; } //printf("sftp_read_sync end\n"); { double sum,avg; // max 28 char + 1 \0: Average temperature: 6553.5 C char str[29]; sum=avg=0; for(int i=0;icans.end->item.volt[i]; avg=sum/VOLTLEN; sprintf(str,"Average voltage: %.3f V",avg); gtk_label_set_text(GTK_LABEL(data->volt_label),str); sum=0; for(int i=0;icans.end->item.temp[i]; avg=sum/TEMPLEN; sprintf(str,"Average temperature: %.1f C",avg); gtk_label_set_text(GTK_LABEL(data->temp_label),str); } gtk_widget_queue_draw(data->area); //return SSH_OK; // must return G_SOURCE_CONTINUE to keep polling // return G_SOURCE_REMOVE to stop return G_SOURCE_CONTINUE; } // https://api.libssh.org/stable/libssh_tutor_guided_tour.html int verify_knownhost(ssh_session session) { enum ssh_known_hosts_e state; unsigned char *hash = NULL; ssh_key srv_pubkey = NULL; size_t hlen; char buf[10]; char *hexa; char *p; int cmp; int rc; rc = ssh_get_server_publickey(session, &srv_pubkey); if (rc < 0) { return -1; } rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen); ssh_key_free(srv_pubkey); if (rc < 0) { return -1; } state = ssh_session_is_known_server(session); switch (state) { case SSH_KNOWN_HOSTS_OK: /* OK */ break; case SSH_KNOWN_HOSTS_CHANGED: fprintf(stderr, "Host key for server changed: it is now:\n"); ssh_print_hexa("Public key hash", hash, hlen); fprintf(stderr, "For security reasons, connection will be stopped\n"); ssh_clean_pubkey_hash(&hash); return -1; case SSH_KNOWN_HOSTS_OTHER: fprintf(stderr, "The host key for this server was not found but an other" "type of key exists.\n"); fprintf(stderr, "An attacker might change the default server key to" "confuse your client into thinking the key does not exist\n"); ssh_clean_pubkey_hash(&hash); return -1; case SSH_KNOWN_HOSTS_NOT_FOUND: fprintf(stderr, "Could not find known host file.\n"); fprintf(stderr, "If you accept the host key here, the file will be" "automatically created.\n"); /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ case SSH_KNOWN_HOSTS_UNKNOWN: hexa = ssh_get_hexa(hash, hlen); fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); fprintf(stderr, "Public key hash: %s\n", hexa); ssh_string_free_char(hexa); ssh_clean_pubkey_hash(&hash); p = fgets(buf, sizeof(buf), stdin); if (p == NULL) { return -1; } cmp = strncasecmp(buf, "yes", 3); if (cmp != 0) { return -1; } rc = ssh_session_update_known_hosts(session); if (rc < 0) { fprintf(stderr, "Error %s\n", strerror(errno)); return -1; } break; case SSH_KNOWN_HOSTS_ERROR: fprintf(stderr, "Error %s", ssh_get_error(session)); ssh_clean_pubkey_hash(&hash); return -1; } ssh_clean_pubkey_hash(&hash); return 0; } // https://plplot.sourceforge.net/documentation.php // https://plplot.sourceforge.net/examples.php // plplot example 0 and ext-cairo-test.c static void draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) { DATA *data=user_data; if(!ListIsEmpty(&(data->cans))) { //printf("draw begin\n"); //PLFLT xmin=data->cans.head->item.t; //PLFLT xmax=data->cans.end->item.t; PLFLT xmin,xmax; PLFLT ymin, ymax; int plot_counts=0; gboolean active_checkbutton[VOLTLEN+TEMPLEN]; if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->live_toggle))) xmax=data->cans.end->item.t; else xmax=data->to_time; if(data->from_time_entered) xmin=data->from_time; else xmin=data->cans.head->item.t; for(int i=0;i<(VOLTLEN+TEMPLEN);i++) if((active_checkbutton[i]=gtk_check_button_get_active(GTK_CHECK_BUTTON(data->checkbutton[i])))) plot_counts++; plsdev( "extcairo" ); { char str[12]; sprintf(str,"%dx%d", gtk_widget_get_width(data->area), gtk_widget_get_height(data->area)); plsetopt("geometry", str); } // change to white background and black foreground, and gray color palette // http://plplot.sourceforge.net/docbook-manual/plplot-html-5.15.0/color.html // seems should be put before plinit; or need pladv(), plvpor(), plwind() for a new picture? // more see example 16 // http://plplot.sourceforge.net/examples.php?demo=16 // /usr/share/plplot5.15.0/examples/c/x16c.c plspal0("cmap0_black_on_white.pal"); plspal1("cmap1_gray.pal", 1); // Initialize plplot if(plot_counts>3) plstar(3,(plot_counts+2)/3); else plstar(1,plot_counts); pl_cmd( PLESC_DEVINIT, cr ); for(int i=0;i<(VOLTLEN+TEMPLEN);i++) { if(active_checkbutton[i]) { if(icans.head; while(pnode!=NULL && ((pnode->item.t <= xmax) || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->live_toggle))) ) { if(pnode->item.t >= xmin) { if(iitem.t,pnode->item.volt[i],1.,0.,0.5,"#(727)"); else plptex(pnode->item.t,pnode->item.temp[i-VOLTLEN],1.,0.,0.5,"#(727)"); } pnode=pnode->next; } } } } // Close PLplot library plend(); } } static void print_data (GtkWidget *widget, gpointer user_data) { DATA *data=user_data; printf("%u",data->cans.end->item.t); for(int i=0;icans.end->item.volt[i]); for(int i=0;icans.end->item.temp[i]); putchar('\n'); } static void set_from_time (GtkWidget *widget, gpointer user_data) { DATA *data=user_data; if(!(data->from_time_entered)) data->from_time_entered=true; data->from_time=strtoul(gtk_editable_get_text(GTK_EDITABLE(data->from_entry)),NULL,10); gtk_widget_queue_draw(data->area); } static void set_to_time (GtkWidget *widget, gpointer user_data) { DATA *data=user_data; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->live_toggle),FALSE); data->to_time=strtoul(gtk_editable_get_text(GTK_EDITABLE(data->to_entry)),NULL,10); gtk_widget_queue_draw(data->area); } static void activate (GtkApplication *app, gpointer user_data) { DATA *data=user_data; GtkWidget *window; data->area = gtk_drawing_area_new (); GtkWidget *button; GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL,1); GtkWidget *scrolled_window; GtkWidget *flowbox_volt = gtk_flow_box_new(); GtkWidget *flowbox_temp = gtk_flow_box_new(); GtkWidget *expander_volt = gtk_expander_new("Choose cell voltages to plot"); GtkWidget *expander_temp = gtk_expander_new("Choose cell temperatures to plot"); GtkWidget *from_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0); GtkWidget *to_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0); GtkWidget *from_button = gtk_button_new_with_label ("Enter from time"); GtkWidget *to_button = gtk_button_new_with_label ("Enter to time"); data->from_entry = gtk_entry_new(); data->to_entry = gtk_entry_new(); // TODO: gtk_entry_set_placeholder_text() //gtk_entry_set_placeholder_text(GTK_ENTRY(data->from_entry),"..."); gtk_entry_set_max_length(GTK_ENTRY(data->from_entry),10); gtk_entry_set_max_length(GTK_ENTRY(data->to_entry),10); gtk_box_append(GTK_BOX(from_box), data->from_entry); gtk_box_append(GTK_BOX(from_box), from_button); gtk_box_append(GTK_BOX(to_box), data->to_entry); gtk_box_append(GTK_BOX(to_box), to_button); g_signal_connect (from_button, "clicked", G_CALLBACK (set_from_time), user_data); g_signal_connect (to_button, "clicked", G_CALLBACK (set_to_time), user_data); data->volt_label=gtk_label_new("Average voltage:"); data->temp_label=gtk_label_new("Average temperature:"); data->live_toggle = gtk_toggle_button_new_with_label("Live"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->live_toggle),TRUE); for(int i=0;i<(VOLTLEN+TEMPLEN);i++) data->checkbutton[i]=gtk_check_button_new_with_label(checkbutton_names[i]); gtk_check_button_set_active(GTK_CHECK_BUTTON(data->checkbutton[0]),TRUE); gtk_check_button_set_active(GTK_CHECK_BUTTON(data->checkbutton[VOLTLEN]),TRUE); window = gtk_application_window_new (app); gtk_window_set_title (GTK_WINDOW (window), "remote_plot"); gtk_window_set_default_size (GTK_WINDOW (window), 1000, 1000); //gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (data->area), 600); //gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (data->area), 600); gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (data->area), draw_function, user_data, NULL); gtk_widget_set_vexpand(data->area,TRUE); gtk_widget_set_hexpand(data->area,TRUE); button = gtk_button_new_with_label ("Print last second data"); g_signal_connect (button, "clicked", G_CALLBACK (print_data), user_data); // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/print-editor/print-editor.c scrolled_window=gtk_scrolled_window_new(); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (scrolled_window), TRUE); gtk_widget_set_vexpand (scrolled_window, TRUE); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), box); for(int i=0;i<(VOLTLEN+TEMPLEN);i++) { if(icheckbutton[i]); else gtk_flow_box_append(GTK_FLOW_BOX(flowbox_temp), data->checkbutton[i]); } gtk_expander_set_child (GTK_EXPANDER (expander_volt), flowbox_volt); gtk_expander_set_child (GTK_EXPANDER (expander_temp), flowbox_temp); gtk_box_append(GTK_BOX(box), button); gtk_box_append(GTK_BOX(box), data->volt_label); gtk_box_append(GTK_BOX(box), data->temp_label); gtk_box_append(GTK_BOX(box), data->live_toggle); gtk_box_append(GTK_BOX(box), from_box); gtk_box_append(GTK_BOX(box), to_box); gtk_box_append(GTK_BOX(box), expander_volt); gtk_box_append(GTK_BOX(box), expander_temp); gtk_box_append(GTK_BOX(box), data->area); gtk_window_set_child (GTK_WINDOW (window), scrolled_window); gtk_window_present (GTK_WINDOW (window)); //printf("before g_timeout_add\n"); g_timeout_add(1000,sftp_read_sync,user_data); //printf("after g_timeout_add\n"); } // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c static int command_line (GApplication *app, GApplicationCommandLine *cmdline, gpointer user_data) { DATA *data=user_data; int rc; GVariantDict *options = g_application_command_line_get_options_dict (cmdline); // not sure if using char * here is ok char *dest="Spartan_Racing_Charger@10.0.0.9"; // https://docs.gtk.org/glib/gvariant-format-strings.html#pointers // &s copy the pointer g_variant_dict_lookup (options, "destination", "&s", &dest); // Open session and set options data->session = ssh_new(); if (data->session == NULL) exit(-1); ssh_options_set(data->session, SSH_OPTIONS_HOST, dest); // Connect to server rc = ssh_connect(data->session); if (rc != SSH_OK) { fprintf(stderr, "Error connecting to %s: %s\n", dest, ssh_get_error(data->session)); ssh_free(data->session); exit(-1); } // Verify the server's identity // For the source code of verify_knownhost(), check previous example if (verify_knownhost(data->session) < 0) { ssh_disconnect(data->session); ssh_free(data->session); exit(-1); } // Authenticate ourselves // https://api.libssh.org/stable/libssh_tutor_authentication.html rc = ssh_userauth_publickey_auto(data->session, NULL, NULL); if (rc != SSH_AUTH_SUCCESS) { fprintf(stderr, "Error authenticating with key: %s\n", ssh_get_error(data->session)); ssh_disconnect(data->session); ssh_free(data->session); exit(-1); } data->sftp = sftp_new(data->session); if (data->sftp == NULL) { fprintf(stderr, "Error allocating SFTP session: %s\n", ssh_get_error(data->session)); return SSH_ERROR; } rc = sftp_init(data->sftp); if (rc != SSH_OK) { fprintf(stderr, "Error initializing SFTP session: code %d.\n", sftp_get_error(data->sftp)); sftp_free(data->sftp); return rc; } // https://discourse.gnome.org/t/gio-application-commandline-does-trigger-activate-signal/2988/22 // > If you pass G_APPLICATION_HANDLES_COMMAND_LINE in the flags, the // > command-line args are passed to the primary instance and ::activate is // > not emitted. An example again is an something like --hide-window or // > --do-without-disturbing-user which wouldn’t work if the default were to // > trigger ::activate and open the window. So just pass // > G_APPLICATION_HANDLES_COMMAND_LINE and then call // > g_application_activate() in your ::command-line handler g_application_activate(app); return 0; } int main (int argc, char **argv) { DATA data; GtkApplication *app; int status; InitializeList(&(data.cans)); if(ListIsFull(&(data.cans))) { fprintf(stderr,"No memory available! Bye!\n"); exit(1); } data.offset=0; data.from_time_entered=false; //printf("after init\n"); // if I don't handle command line //app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); // if I handle command line app = gtk_application_new ("org.gtk.example", G_APPLICATION_HANDLES_COMMAND_LINE); g_application_add_main_option(G_APPLICATION(app),"destination",'d',0,G_OPTION_ARG_STRING,"ssh destination","Spartan_Racing_Charger@10.0.0.9"); g_signal_connect (app, "activate", G_CALLBACK (activate), &data); g_signal_connect (app, "command-line", G_CALLBACK (command_line), &data); //printf("before g_app run\n"); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); //printf("before ssh free\n"); ssh_disconnect(data.session); ssh_free(data.session); // Segmentation fault (core dumped) error, why? maybe double free? so no free is ok? //sftp_free(sftp); EmptyTheList(&(data.cans)); return status; }