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 #define _(text) gettext(text)
63 #include "dolconf-assistant.gtkh"
64 #include "dolconf-wnd.gtkh"
66 int v_nonempty(const char *val)
68 return(strlen(val) > 0);
71 int v_dcstring(const char *val)
73 return((strchr(val, ' ') == NULL) &&
74 (strchr(val, '|') == NULL) &&
75 (strchr(val, '$') == NULL));
78 int v_natural(const char *val)
89 int v_integer(const char *val)
93 for(f = 1, d = 0; *val; val++, f = 0) {
96 } else if(f && (*val == '-')) {
104 int v_ipv4(const char *val)
108 return(inet_aton(val, &buf) != 0);
114 struct validation nonempty = {
116 .invmsg = _("%s must not be empty"),
119 struct validation dcstring = {
121 .invmsg = _("%s must not contain spaces, `|' or `$'"),
124 struct validation natural = {
126 .invmsg = _("%s must be a natural number"),
129 struct validation integer = {
131 .invmsg = _("%s must be an integer"),
134 struct validation ipv4 = {
136 .invmsg = _("%s must be an IP address"),
139 struct validation *vldxlate[] = {
140 &nonempty, &dcstring, &natural, &integer, &ipv4,
144 struct cfvar config[] = {
145 {"cli.defnick", _("Screen name"), "", &dcstring, &ast_nick, &cfw_nick},
146 {"net.mode", NULL, "0", &natural},
147 {"net.visibleipv4", "IP address", "0.0.0.0", &ipv4, NULL, &cfw_extip},
148 {"ui.onlylocal", NULL, "0", &natural},
149 {"ui.port", NULL, "-1", &integer},
150 {"auth.authless", NULL, "0", &natural},
151 {"transfer.slots", _("Upload slots"), "6", &natural},
152 {"dc.speedstring", _("Connection speed"), "DSL", &dcstring, NULL, &cfw_cntype},
153 {"dc.email", _("E-mail address"), "spam@spam.net", &dcstring, NULL, &cfw_mail},
154 {"dc.desc", _("Share description"), "", NULL, &ast_desc, &cfw_desc},
155 {"dc.tcpport", _("Direct Connect TCP port"), "0", &natural, NULL, &cfw_tcpport},
156 {"dc.udpport", _("Direct Connect UDP port"), "0", &natural, NULL, &cfw_udpport},
161 #define _(text) gettext(text)
163 struct cfvar *findcfvar(char *name)
167 for(v = config; v->name != NULL; v++) {
168 if(!strcmp(v->name, name))
174 void setcfvar(char *name, const char *val)
179 if(!strcmp(v->val, val))
182 v->val = sstrdup(val);
186 int msgbox(int type, int buttons, char *format, ...)
193 va_start(args, format);
194 buf = vsprintf2(format, args);
196 swnd = gtk_message_dialog_new(rootwnd, GTK_DIALOG_MODAL, type, buttons, "%s", buf);
197 gtk_window_set_title(GTK_WINDOW(swnd), _("Dolda Connect configurator"));
198 resp = gtk_dialog_run(GTK_DIALOG(swnd));
199 gtk_widget_destroy(swnd);
204 void prepstatic(void)
206 struct validation **v;
209 for(v = vldxlate; *v != NULL; v++)
210 (*v)->invmsg = gettext((*v)->invmsg);
211 for(c = config; c->name != NULL; c++) {
213 c->rname = gettext(c->rname);
214 c->val = sstrdup(c->val);
218 char *getword(char **p)
220 char *buf, *p2, delim;
228 while((p2 = strchr(p2 + 1, delim)) != NULL) {
233 p2 = *p + strlen(*p);
234 len = p2 - *p - ((*p2 == '\"')?1:0);
235 buf = smalloc(len + 1);
236 memcpy(buf, *p + ((delim == '\"')?1:0), len);
238 *p = p2 + ((*p2 == '\"')?1:0);
239 for(p2 = buf; *p2; p2++, len--) {
241 memmove(p2, p2 + 1, len--);
246 char *quoteword(char *word)
258 for(wp = word; *wp != '\0'; wp++)
260 if(!dq && isspace(*wp))
262 if((*wp == '\\') || (*wp == '\"'))
269 bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
272 for(wp = word; *wp != '\0'; wp++)
274 if((*wp == '\\') || (*wp == '\"'))
295 if((cf = fopen(cfname, "r")) == NULL) {
296 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for reading: %s"), strerror(errno));
300 while(fgets(lbuf, sizeof(lbuf), cf) != NULL) {
302 if(lbuf[len - 1] == '\n')
312 if(!strncmp(lbuf, "set ", 4)) {
314 if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
318 for(var = config; var->name != NULL; var++) {
319 if(!strcmp(var->name, key)) {
321 var->val = sstrdup(val);
325 if(var->name == NULL)
327 } else if(!strncmp(lbuf, "share ", 6)) {
329 if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
333 gtk_list_store_append(shares, &iter);
334 gtk_list_store_set(shares, &iter, 0, key, 1, val, -1);
335 } else if(!lbuf[0] || lbuf[0] == '#') {
349 int writeconfig(void)
356 if((cf = fopen(cfname, "w")) == NULL) {
357 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for writing: %s"), strerror(errno));
360 fputs("# This file was generated by dolconf v" VERSION "\n\n", cf);
361 for(var = config; var->name != NULL; var++) {
362 fprintf(cf, "set %s ", var->name);
363 if((buf = quoteword(var->val)) == NULL) {
371 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
375 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 0, &buf2, -1);
376 if((buf = quoteword(buf2)) == NULL) {
384 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf2, -1);
385 if((buf = quoteword(buf2)) == NULL) {
393 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
404 for(var = config; var->name != NULL; var++) {
405 if(var->cfww != NULL)
406 gtk_entry_set_text(GTK_ENTRY(*(var->cfww)), var->val);
408 if(atoi(findcfvar("dc.tcpport")->val) || atoi(findcfvar("dc.udpport")->val))
409 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), TRUE);
411 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), FALSE);
412 if(strcmp(findcfvar("net.visibleipv4")->val, "0.0.0.0"))
413 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), TRUE);
415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), FALSE);
416 if(strcmp(findcfvar("ui.port")->val, "-1")) {
417 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), TRUE);
418 if(strcmp(findcfvar("auth.authless")->val, "1"))
419 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
421 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), TRUE);
423 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), FALSE);
424 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
430 setcfvar("cli.defnick", gtk_entry_get_text(GTK_ENTRY(ast_nick)));
431 setcfvar("dc.desc", gtk_entry_get_text(GTK_ENTRY(ast_desc)));
432 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_psv))) {
433 setcfvar("net.mode", "1");
435 setcfvar("net.mode", "0");
436 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_nat))) {
437 setcfvar("net.visibleipv4", gtk_entry_get_text(GTK_ENTRY(ast_extip)));
438 setcfvar("dc.tcpport", gtk_entry_get_text(GTK_ENTRY(ast_udpport)));
439 setcfvar("dc.udpport", gtk_entry_get_text(GTK_ENTRY(ast_tcpport)));
441 setcfvar("net.visibleipv4", "0.0.0.0");
442 setcfvar("dc.tcpport", "0");
443 setcfvar("dc.udpport", "0");
453 for(var = config; var->name != NULL; var++) {
454 if(var->cfww != NULL) {
455 val = gtk_entry_get_text(GTK_ENTRY(*(var->cfww)));
456 if(!strcmp(var->val, val))
459 var->val = sstrdup(val);
463 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_mode_act))) {
464 setcfvar("net.mode", "0");
465 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_orport))) {
466 setcfvar("dc.tcpport", "0");
467 setcfvar("dc.udpport", "0");
469 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_oraddr))) {
470 setcfvar("net.visibleipv4", "0.0.0.0");
473 setcfvar("net.mode", "1");
475 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_uinet))) {
476 setcfvar("ui.port", "1500");
477 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_authless)))
478 setcfvar("auth.authless", "1");
480 setcfvar("auth.authless", "0");
482 setcfvar("ui.port", "-1");
483 setcfvar("auth.authless", "0");
487 struct cfvar *cfwvalid(void)
491 for(cv = config; cv->name != NULL; cv++) {
492 if((cv->vld != NULL) && !cv->vld->check(cv->val)) {
496 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Auto-generated variable %s has an invalid value \"%s\")"), cv->name, cv->val);
504 void astcancel(GtkWidget *widget, gpointer uudata)
512 #define bufcats(buf, str) bufcat(buf, str, strlen(str))
514 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata)
524 for(var = config; var->name != NULL; var++) {
525 if(var->rname == NULL)
527 bufcats(s, var->rname);
529 bufcat(s, var->val, strlen(var->val));
532 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
534 bufcats(s, _("Shares:\n"));
537 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf, -1);
541 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
543 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ast_summary)), s, sdata);
547 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
551 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_dolcon)))
553 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_exit)))
560 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata)
562 if(v_dcstring(gtk_entry_get_text(GTK_ENTRY(ast_nick))))
563 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, TRUE);
565 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, FALSE);
568 int hasshare(int col, char *name)
573 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
575 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, col, &buf, -1);
576 if(!strcmp(buf, name)) {
581 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
595 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);
596 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chd), TRUE);
597 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chd), TRUE);
598 resp = gtk_dialog_run(GTK_DIALOG(chd));
599 if(resp != GTK_RESPONSE_ACCEPT) {
600 gtk_widget_destroy(chd);
604 fns = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chd));
605 gtk_widget_destroy(chd);
608 if(!hasshare(1, fn)) {
609 if((p = strrchr(fn, '/')) == NULL)
614 if(hasshare(0, sn)) {
617 sn = sprintf2("%s%i", p, i);
622 gtk_list_store_append(shares, &iter);
623 gtk_list_store_set(shares, &iter, 0, sn, 1, fn, -1);
635 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata)
638 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, TRUE);
641 void cb_cfw_shareadd_clicked(GtkWidget *widget, gpointer uudata)
647 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata)
651 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(ast_sharelist)), NULL, &iter))
652 gtk_list_store_remove(shares, &iter);
653 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(shares), NULL) == 0)
654 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, FALSE);
657 void cb_cfw_sharerem_clicked(GtkWidget *widget, gpointer uudata)
661 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(cfw_sharelist)), NULL, &iter)) {
662 gtk_list_store_remove(shares, &iter);
667 void cb_ast_checkports(GtkWidget *widget, gpointer uudata)
669 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3,
670 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) &&
671 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) >= 1024) &&
672 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) &&
673 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) >= 1024) &&
674 v_ipv4(gtk_entry_get_text(GTK_ENTRY(ast_extip))));
677 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata)
679 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
680 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), TRUE);
681 cb_ast_checkports(widget, NULL);
683 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), FALSE);
684 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3, TRUE);
688 void cb_cfw_mode_act_toggled(GtkWidget *widget, gpointer uudata)
690 gtk_widget_set_sensitive(GTK_WIDGET(cfw_natbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
693 void cb_cfw_orport_toggled(GtkWidget *widget, gpointer uudata)
695 gtk_widget_set_sensitive(GTK_WIDGET(cfw_portbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
698 void cb_cfw_oraddr_toggled(GtkWidget *widget, gpointer uudata)
700 gtk_widget_set_sensitive(GTK_WIDGET(cfw_addrbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
703 void cb_cfw_uinet_toggled(GtkWidget *widget, gpointer uudata)
705 gtk_widget_set_sensitive(GTK_WIDGET(cfw_uibox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
708 int hupserver(int disperr)
711 struct dc_response *resp;
713 if(dc_connectsync2(dc_srv_local, DC_LATEST) < 0) {
715 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
718 if(dc_login(NULL, 1, dc_convnone, NULL) != DC_LOGIN_ERR_SUCCESS) {
721 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
725 tag = dc_queuecmd(NULL, NULL, L"hup", NULL);
726 if((resp = dc_gettaggedrespsync(tag)) != NULL) {
727 if(resp->code != 200) {
729 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
743 if((cv = cfwvalid()) != NULL) {
744 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, cv->vld->invmsg, cv->rname);
747 return(writeconfig());
751 void cb_cfw_hup_activate(GtkWidget *widget, gpointer uudata)
756 void cb_cfw_save_activate(GtkWidget *widget, gpointer uudata)
760 if((cv = cfwvalid()) != NULL) {
761 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, cv->vld->invmsg, cv->rname);
768 void cb_cfw_quit_activate(GtkWidget *widget, gpointer uudata)
772 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)
780 void cb_cfw_ok_clicked(GtkWidget *widget, gpointer uudata)
789 void cb_cfw_cancel_clicked(GtkWidget *widget, gpointer uudata)
795 void cb_cfw_apply_clicked(GtkWidget *widget, gpointer uudata)
802 int main(int argc, char **argv)
807 setlocale(LC_ALL, "");
808 bindtextdomain(PACKAGE, LOCALEDIR);
813 gtk_init(&argc, &argv);
815 while((c = getopt(argc, argv, "hawV")) != -1) {
824 printf("usage: dolconf [-haw]\n");
825 printf("\t-h\tDisplay this help message\n");
826 printf("\t-a\tGo directly to the assistant\n");
827 printf("\t-w\tGo directly to the standard window\n");
828 printf("\t-V\tDisplay version info and exit\n");
831 printf("%s", RELEASEINFO);
834 fprintf(stderr, "usage: dolconf [-hawV]\n");
841 shares = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
842 gtk_tree_view_set_model(GTK_TREE_VIEW(ast_sharelist), GTK_TREE_MODEL(shares));
843 gtk_tree_view_set_model(GTK_TREE_VIEW(cfw_sharelist), GTK_TREE_MODEL(shares));
846 if(getenv("HOME") != NULL) {
847 cfname = sprintf2("%s/.doldacond.conf", getenv("HOME"));
849 if((pwd = getpwuid(getuid())) != NULL)
850 cfname = sprintf2("%s/.doldacond.conf", pwd->pw_dir);
853 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
857 ex = !access(cfname, F_OK);
860 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)
869 if(ex && (state == 0)) {
870 if(readconfig() == 1) {
871 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)
878 gtk_window_set_default_size(GTK_WINDOW(cfw_wnd), 500, 350);
879 gtk_widget_show(cfw_wnd);
881 rootwnd = GTK_WINDOW(cfw_wnd);
883 gtk_widget_hide(cfw_wnd);
885 } else if(state == 1) {
886 gtk_window_set_default_size(GTK_WINDOW(ast_wnd), 500, 350);
887 gtk_widget_show(ast_wnd);
888 rootwnd = GTK_WINDOW(ast_wnd);
890 gtk_widget_hide(ast_wnd);
893 } else if(state == 2) {
894 for(i = 3; i < FD_SETSIZE; i++)
896 execlp("dolcon-launch", "dolcon-launch", NULL);
897 perror("dolcon-launch");
900 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Unknown state)"));
907 #include "dolconf-assistant.gtk"
908 #include "dolconf-wnd.gtk"