2 * Dolda Connect - Modular multiuser Direct Connect-style client
3 * Copyright (C) 2007 Fredrik Tolf <fredrik@dolda2000.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include <gdk/gdkkeysyms.h>
33 #include <arpa/inet.h>
34 #include <doldaconnect/uilib.h>
35 #include <doldaconnect/uimisc.h>
43 int (*check)(const char *val);
51 struct validation *vld;
52 GtkWidget **astw, **cfww;
56 GtkWindow *rootwnd = NULL;
61 void astcancel(GtkWidget *widget, gpointer uudata);
62 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata);
63 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata);
64 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata);
65 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata);
66 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata);
67 void cb_ast_checkports(GtkWidget *widget, gpointer uudata);
68 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata);
69 void cb_cfw_mode_act_toggled(GtkWidget *widget, gpointer uudata);
70 void cb_cfw_orport_toggled(GtkWidget *widget, gpointer uudata);
71 void cb_cfw_oraddr_toggled(GtkWidget *widget, gpointer uudata);
72 void cb_cfw_uinet_toggled(GtkWidget *widget, gpointer uudata);
73 void cb_cfw_save_activate(GtkWidget *widget, gpointer uudata);
74 void cb_cfw_hup_activate(GtkWidget *widget, gpointer uudata);
75 void cb_cfw_quit_activate(GtkWidget *widget, gpointer uudata);
76 void cb_cfw_shareadd_clicked(GtkWidget *widget, gpointer uudata);
77 void cb_cfw_sharerem_clicked(GtkWidget *widget, gpointer uudata);
79 #define _(text) gettext(text)
81 #include "dolconf-assistant.gtk"
82 #include "dolconf-wnd.gtk"
84 int v_nonempty(const char *val)
86 return(strlen(val) > 0);
89 int v_dcstring(const char *val)
91 return((strchr(val, ' ') == NULL) &&
92 (strchr(val, '|') == NULL) &&
93 (strchr(val, '$') == NULL));
96 int v_natural(const char *val)
107 int v_integer(const char *val)
111 for(f = 1, d = 0; *val; val++, f = 0) {
114 } else if(f && (*val == '-')) {
122 int v_ipv4(const char *val)
126 return(inet_aton(val, &buf) != 0);
132 struct validation nonempty = {
134 .invmsg = _("%s must not be empty"),
137 struct validation dcstring = {
139 .invmsg = _("%s must not contain spaces, `|' or `$'"),
142 struct validation natural = {
144 .invmsg = _("%s must be a natural number"),
147 struct validation integer = {
149 .invmsg = _("%s must be an integer"),
152 struct validation ipv4 = {
154 .invmsg = _("%s must be an IP address"),
157 struct validation *vldxlate[] = {
158 &nonempty, &dcstring, &natural, &integer, &ipv4,
162 struct cfvar config[] = {
163 {"cli.defnick", _("Screen name"), "", &dcstring, &ast_nick, &cfw_nick},
164 {"net.mode", NULL, "0", &natural},
165 {"net.visibleipv4", "IP address", "0.0.0.0", &ipv4, NULL, &cfw_extip},
166 {"ui.onlylocal", NULL, "0", &natural},
167 {"ui.port", NULL, "-1", &integer},
168 {"auth.authless", NULL, "0", &natural},
169 {"transfer.slots", _("Upload slots"), "6", &natural},
170 {"dc.speedstring", _("Connection speed"), "DSL", &dcstring, NULL, &cfw_cntype},
171 {"dc.email", _("E-mail address"), "spam@spam.net", &dcstring, NULL, &cfw_mail},
172 {"dc.desc", _("Share description"), "", NULL, &ast_desc, &cfw_desc},
173 {"dc.tcpport", _("Direct Connect TCP port"), "0", &natural, NULL, &cfw_tcpport},
174 {"dc.udpport", _("Direct Connect UDP port"), "0", &natural, NULL, &cfw_udpport},
179 #define _(text) gettext(text)
181 struct cfvar *findcfvar(char *name)
185 for(v = config; v->name != NULL; v++) {
186 if(!strcmp(v->name, name))
192 void setcfvar(char *name, const char *val)
197 if(!strcmp(v->val, val))
200 v->val = sstrdup(val);
204 int msgbox(int type, int buttons, char *format, ...)
211 va_start(args, format);
212 buf = vsprintf2(format, args);
214 swnd = gtk_message_dialog_new(rootwnd, GTK_DIALOG_MODAL, type, buttons, "%s", buf);
215 gtk_window_set_title(GTK_WINDOW(swnd), _("Dolda Connect configurator"));
216 resp = gtk_dialog_run(GTK_DIALOG(swnd));
217 gtk_widget_destroy(swnd);
222 void prepstatic(void)
224 struct validation **v;
227 for(v = vldxlate; *v != NULL; v++)
228 (*v)->invmsg = gettext((*v)->invmsg);
229 for(c = config; c->name != NULL; c++) {
231 c->rname = gettext(c->rname);
232 c->val = sstrdup(c->val);
236 char *getword(char **p)
238 char *buf, *p2, delim;
246 while((p2 = strchr(p2 + 1, delim)) != NULL) {
251 p2 = *p + strlen(*p);
252 len = p2 - *p - ((*p2 == '\"')?1:0);
253 buf = smalloc(len + 1);
254 memcpy(buf, *p, len);
256 *p = p2 + ((*p2 == '\"')?1:0);
257 for(p2 = buf; *p2; p2++, len--) {
259 memmove(p2, p2 + 1, len--);
264 char *quoteword(char *word)
276 for(wp = word; *wp != '\0'; wp++)
278 if(!dq && isspace(*wp))
280 if((*wp == '\\') || (*wp == '\"'))
287 bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
290 for(wp = word; *wp != '\0'; wp++)
292 if((*wp == '\\') || (*wp == '\"'))
313 if((cf = fopen(cfname, "r")) == NULL) {
314 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for reading: %s"), strerror(errno));
318 while(fgets(lbuf, sizeof(lbuf), cf) != NULL) {
320 if(lbuf[len - 1] == '\n')
330 if(!strncmp(lbuf, "set ", 4)) {
332 if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
336 for(var = config; var->name != NULL; var++) {
337 if(!strcmp(var->name, key)) {
339 var->val = sstrdup(val);
343 if(var->name == NULL)
345 } else if(!strncmp(lbuf, "share ", 6)) {
347 if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
351 gtk_list_store_append(shares, &iter);
352 gtk_list_store_set(shares, &iter, 0, key, 1, val, -1);
353 } else if(!lbuf[0] || lbuf[0] == '#') {
367 void writeconfig(void)
374 if((cf = fopen(cfname, "w")) == NULL) {
375 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for writing: %s"), strerror(errno));
378 fputs("# This file was generated by dolconf v" VERSION "\n\n", cf);
379 for(var = config; var->name != NULL; var++) {
380 fprintf(cf, "set %s ", var->name);
381 if((buf = quoteword(var->val)) == NULL) {
389 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
393 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 0, &buf2, -1);
394 if((buf = quoteword(buf2)) == NULL) {
402 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf2, -1);
403 if((buf = quoteword(buf2)) == NULL) {
411 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
421 for(var = config; var->name != NULL; var++) {
422 if(var->cfww != NULL)
423 gtk_entry_set_text(GTK_ENTRY(*(var->cfww)), var->val);
425 if(atoi(findcfvar("dc.tcpport")->val) || atoi(findcfvar("dc.udpport")->val))
426 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), TRUE);
428 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), FALSE);
429 if(strcmp(findcfvar("net.visibleipv4")->val, "0.0.0.0"))
430 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), TRUE);
432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), FALSE);
433 if(strcmp(findcfvar("ui.port")->val, "-1")) {
434 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), TRUE);
435 if(strcmp(findcfvar("auth.authless")->val, "1"))
436 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
438 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), TRUE);
440 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), FALSE);
441 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
447 setcfvar("cli.defnick", gtk_entry_get_text(GTK_ENTRY(ast_nick)));
448 setcfvar("dc.desc", gtk_entry_get_text(GTK_ENTRY(ast_desc)));
449 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_psv))) {
450 setcfvar("net.mode", "1");
452 setcfvar("net.mode", "0");
453 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_nat))) {
454 setcfvar("net.visibleipv4", gtk_entry_get_text(GTK_ENTRY(ast_extip)));
455 setcfvar("dc.tcpport", gtk_entry_get_text(GTK_ENTRY(ast_udpport)));
456 setcfvar("dc.udpport", gtk_entry_get_text(GTK_ENTRY(ast_tcpport)));
458 setcfvar("net.visibleipv4", "0.0.0.0");
459 setcfvar("dc.tcpport", "0");
460 setcfvar("dc.udpport", "0");
470 for(var = config; var->name != NULL; var++) {
471 if(var->cfww != NULL) {
472 val = gtk_entry_get_text(GTK_ENTRY(*(var->cfww)));
473 if(!strcmp(var->val, val))
476 var->val = sstrdup(val);
480 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_mode_act))) {
481 setcfvar("net.mode", "0");
482 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_orport))) {
483 setcfvar("dc.tcpport", "0");
484 setcfvar("dc.udpport", "0");
486 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_oraddr))) {
487 setcfvar("net.visibleipv4", "0.0.0.0");
490 setcfvar("net.mode", "1");
492 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_uinet))) {
493 setcfvar("ui.port", "1500");
494 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_authless)))
495 setcfvar("auth.authless", "1");
497 setcfvar("auth.authless", "0");
499 setcfvar("ui.port", "-1");
500 setcfvar("auth.authless", "0");
504 struct cfvar *cfwvalid(void)
508 for(cv = config; cv->name != NULL; cv++) {
509 if((cv->vld != NULL) && !cv->vld->check(cv->val)) {
513 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Auto-generated variable %s has an invalid value \"%s\")"), cv->name, cv->val);
521 void astcancel(GtkWidget *widget, gpointer uudata)
529 #define bufcats(buf, str) bufcat(buf, str, strlen(str))
531 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata)
541 for(var = config; var->name != NULL; var++) {
542 if(var->rname == NULL)
544 bufcats(s, var->rname);
546 bufcat(s, var->val, strlen(var->val));
549 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
551 bufcats(s, _("Shares:\n"));
554 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf, -1);
558 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
560 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ast_summary)), s, sdata);
564 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
568 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_dolcon)))
570 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_exit)))
577 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata)
579 if(v_dcstring(gtk_entry_get_text(GTK_ENTRY(ast_nick))))
580 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, TRUE);
582 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, FALSE);
585 int hasshare(int col, char *name)
590 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
592 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, col, &buf, -1);
593 if(!strcmp(buf, name)) {
598 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
612 chd = gtk_file_chooser_dialog_new(_("Shared directories"), rootwnd, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
613 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chd), TRUE);
614 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chd), TRUE);
615 resp = gtk_dialog_run(GTK_DIALOG(chd));
616 if(resp != GTK_RESPONSE_ACCEPT) {
617 gtk_widget_destroy(chd);
621 fns = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chd));
622 gtk_widget_destroy(chd);
625 if(!hasshare(1, fn)) {
626 if((p = strrchr(fn, '/')) == NULL)
631 if(hasshare(0, sn)) {
634 sn = sprintf2("%s%i", p, i);
639 gtk_list_store_append(shares, &iter);
640 gtk_list_store_set(shares, &iter, 0, sn, 1, fn, -1);
652 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata)
655 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, TRUE);
658 void cb_cfw_shareadd_clicked(GtkWidget *widget, gpointer uudata)
663 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata)
667 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(ast_sharelist)), NULL, &iter))
668 gtk_list_store_remove(shares, &iter);
669 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(shares), NULL) == 0)
670 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, FALSE);
673 void cb_cfw_sharerem_clicked(GtkWidget *widget, gpointer uudata)
677 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(cfw_sharelist)), NULL, &iter))
678 gtk_list_store_remove(shares, &iter);
681 void cb_ast_checkports(GtkWidget *widget, gpointer uudata)
683 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3,
684 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) &&
685 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) >= 1024) &&
686 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) &&
687 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) >= 1024) &&
688 v_ipv4(gtk_entry_get_text(GTK_ENTRY(ast_extip))));
691 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata)
693 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
694 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), TRUE);
695 cb_ast_checkports(widget, NULL);
697 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), FALSE);
698 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3, TRUE);
702 void cb_cfw_mode_act_toggled(GtkWidget *widget, gpointer uudata)
704 gtk_widget_set_sensitive(GTK_WIDGET(cfw_natbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
707 void cb_cfw_orport_toggled(GtkWidget *widget, gpointer uudata)
709 gtk_widget_set_sensitive(GTK_WIDGET(cfw_portbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
712 void cb_cfw_oraddr_toggled(GtkWidget *widget, gpointer uudata)
714 gtk_widget_set_sensitive(GTK_WIDGET(cfw_addrbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
717 void cb_cfw_uinet_toggled(GtkWidget *widget, gpointer uudata)
719 gtk_widget_set_sensitive(GTK_WIDGET(cfw_uibox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
722 void cb_cfw_hup_activate(GtkWidget *widget, gpointer uudata)
725 struct dc_response *resp;
727 if(dc_connectsync2(dc_srv_local, DC_LATEST) < 0) {
728 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
731 if(dc_login(NULL, 1, dc_convnone, NULL) != DC_LOGIN_ERR_SUCCESS) {
733 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
736 tag = dc_queuecmd(NULL, NULL, L"hup", NULL);
737 if((resp = dc_gettaggedrespsync(tag)) != NULL) {
738 if(resp->code != 200)
739 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
745 void cb_cfw_save_activate(GtkWidget *widget, gpointer uudata)
749 if((cv = cfwvalid()) != NULL) {
750 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, cv->vld->invmsg, cv->rname);
757 void cb_cfw_quit_activate(GtkWidget *widget, gpointer uudata)
761 if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("There are unsaved changes. Do you wish to discard the changes and exit anyway?")) == GTK_RESPONSE_NO)
768 int main(int argc, char **argv)
773 setlocale(LC_ALL, "");
774 bindtextdomain(PACKAGE, LOCALEDIR);
779 gtk_init(&argc, &argv);
781 while((c = getopt(argc, argv, "haw")) != -1) {
790 printf("usage: dolconf [-haw]\n");
791 printf("\t-h\tDisplay this help message\n");
792 printf("\t-a\tGo directly to the assistant\n");
793 printf("\t-w\tGo directly to the standard window\n");
796 fprintf(stderr, "usage: dolconf [-haw]\n");
803 shares = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
804 gtk_tree_view_set_model(GTK_TREE_VIEW(ast_sharelist), GTK_TREE_MODEL(shares));
805 gtk_tree_view_set_model(GTK_TREE_VIEW(cfw_sharelist), GTK_TREE_MODEL(shares));
808 if(getenv("HOME") != NULL) {
809 cfname = sprintf2("%s/.doldacond.conf", getenv("HOME"));
811 if((pwd = getpwuid(getuid())) != NULL)
812 cfname = sprintf2("%s/.doldacond.conf", pwd->pw_dir);
815 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
819 ex = !access(cfname, F_OK);
822 if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("It appears that you have not run this setup program before. Would you like to run the first-time setup assistant?")) == GTK_RESPONSE_YES)
831 if(ex && (state == 0)) {
832 if(readconfig() == 1) {
833 if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("The configuration file appears to have been edited outside the control of this program. If you continue using this program, all settings not handled by it will be lost. Do you wish to continue?")) == GTK_RESPONSE_NO)
840 gtk_window_set_default_size(GTK_WINDOW(cfw_wnd), 500, 350);
841 gtk_widget_show(cfw_wnd);
843 rootwnd = GTK_WINDOW(cfw_wnd);
845 gtk_widget_hide(cfw_wnd);
847 } else if(state == 1) {
848 gtk_window_set_default_size(GTK_WINDOW(ast_wnd), 500, 350);
849 gtk_widget_show(ast_wnd);
850 rootwnd = GTK_WINDOW(ast_wnd);
852 gtk_widget_hide(ast_wnd);
855 } else if(state == 2) {
856 for(i = 3; i < FD_SETSIZE; i++)
858 execlp("dolcon", "dolcon", NULL);
862 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Unknown state)"));