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, 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 void 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));
403 for(var = config; var->name != NULL; var++) {
404 if(var->cfww != NULL)
405 gtk_entry_set_text(GTK_ENTRY(*(var->cfww)), var->val);
407 if(atoi(findcfvar("dc.tcpport")->val) || atoi(findcfvar("dc.udpport")->val))
408 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), TRUE);
410 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_orport), FALSE);
411 if(strcmp(findcfvar("net.visibleipv4")->val, "0.0.0.0"))
412 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), TRUE);
414 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_oraddr), FALSE);
415 if(strcmp(findcfvar("ui.port")->val, "-1")) {
416 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), TRUE);
417 if(strcmp(findcfvar("auth.authless")->val, "1"))
418 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
420 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), TRUE);
422 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_uinet), FALSE);
423 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfw_authless), FALSE);
429 setcfvar("cli.defnick", gtk_entry_get_text(GTK_ENTRY(ast_nick)));
430 setcfvar("dc.desc", gtk_entry_get_text(GTK_ENTRY(ast_desc)));
431 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_psv))) {
432 setcfvar("net.mode", "1");
434 setcfvar("net.mode", "0");
435 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_nat))) {
436 setcfvar("net.visibleipv4", gtk_entry_get_text(GTK_ENTRY(ast_extip)));
437 setcfvar("dc.tcpport", gtk_entry_get_text(GTK_ENTRY(ast_udpport)));
438 setcfvar("dc.udpport", gtk_entry_get_text(GTK_ENTRY(ast_tcpport)));
440 setcfvar("net.visibleipv4", "0.0.0.0");
441 setcfvar("dc.tcpport", "0");
442 setcfvar("dc.udpport", "0");
452 for(var = config; var->name != NULL; var++) {
453 if(var->cfww != NULL) {
454 val = gtk_entry_get_text(GTK_ENTRY(*(var->cfww)));
455 if(!strcmp(var->val, val))
458 var->val = sstrdup(val);
462 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_mode_act))) {
463 setcfvar("net.mode", "0");
464 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_orport))) {
465 setcfvar("dc.tcpport", "0");
466 setcfvar("dc.udpport", "0");
468 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_oraddr))) {
469 setcfvar("net.visibleipv4", "0.0.0.0");
472 setcfvar("net.mode", "1");
474 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_uinet))) {
475 setcfvar("ui.port", "1500");
476 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cfw_authless)))
477 setcfvar("auth.authless", "1");
479 setcfvar("auth.authless", "0");
481 setcfvar("ui.port", "-1");
482 setcfvar("auth.authless", "0");
486 struct cfvar *cfwvalid(void)
490 for(cv = config; cv->name != NULL; cv++) {
491 if((cv->vld != NULL) && !cv->vld->check(cv->val)) {
495 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Auto-generated variable %s has an invalid value \"%s\")"), cv->name, cv->val);
503 void astcancel(GtkWidget *widget, gpointer uudata)
511 #define bufcats(buf, str) bufcat(buf, str, strlen(str))
513 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata)
523 for(var = config; var->name != NULL; var++) {
524 if(var->rname == NULL)
526 bufcats(s, var->rname);
528 bufcat(s, var->val, strlen(var->val));
531 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
533 bufcats(s, _("Shares:\n"));
536 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf, -1);
540 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
542 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ast_summary)), s, sdata);
546 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
550 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_dolcon)))
552 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_action_exit)))
559 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata)
561 if(v_dcstring(gtk_entry_get_text(GTK_ENTRY(ast_nick))))
562 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, TRUE);
564 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, FALSE);
567 int hasshare(int col, char *name)
572 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
574 gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, col, &buf, -1);
575 if(!strcmp(buf, name)) {
580 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
594 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);
595 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chd), TRUE);
596 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chd), TRUE);
597 resp = gtk_dialog_run(GTK_DIALOG(chd));
598 if(resp != GTK_RESPONSE_ACCEPT) {
599 gtk_widget_destroy(chd);
603 fns = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chd));
604 gtk_widget_destroy(chd);
607 if(!hasshare(1, fn)) {
608 if((p = strrchr(fn, '/')) == NULL)
613 if(hasshare(0, sn)) {
616 sn = sprintf2("%s%i", p, i);
621 gtk_list_store_append(shares, &iter);
622 gtk_list_store_set(shares, &iter, 0, sn, 1, fn, -1);
634 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata)
637 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, TRUE);
640 void cb_cfw_shareadd_clicked(GtkWidget *widget, gpointer uudata)
645 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata)
649 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(ast_sharelist)), NULL, &iter))
650 gtk_list_store_remove(shares, &iter);
651 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(shares), NULL) == 0)
652 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, FALSE);
655 void cb_cfw_sharerem_clicked(GtkWidget *widget, gpointer uudata)
659 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(cfw_sharelist)), NULL, &iter))
660 gtk_list_store_remove(shares, &iter);
663 void cb_ast_checkports(GtkWidget *widget, gpointer uudata)
665 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3,
666 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) &&
667 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) >= 1024) &&
668 v_natural(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) &&
669 (atoi(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) >= 1024) &&
670 v_ipv4(gtk_entry_get_text(GTK_ENTRY(ast_extip))));
673 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata)
675 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
676 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), TRUE);
677 cb_ast_checkports(widget, NULL);
679 gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), FALSE);
680 gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3, TRUE);
684 void cb_cfw_mode_act_toggled(GtkWidget *widget, gpointer uudata)
686 gtk_widget_set_sensitive(GTK_WIDGET(cfw_natbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
689 void cb_cfw_orport_toggled(GtkWidget *widget, gpointer uudata)
691 gtk_widget_set_sensitive(GTK_WIDGET(cfw_portbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
694 void cb_cfw_oraddr_toggled(GtkWidget *widget, gpointer uudata)
696 gtk_widget_set_sensitive(GTK_WIDGET(cfw_addrbox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
699 void cb_cfw_uinet_toggled(GtkWidget *widget, gpointer uudata)
701 gtk_widget_set_sensitive(GTK_WIDGET(cfw_uibox), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
704 void cb_cfw_hup_activate(GtkWidget *widget, gpointer uudata)
707 struct dc_response *resp;
709 if(dc_connectsync2(dc_srv_local, DC_LATEST) < 0) {
710 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
713 if(dc_login(NULL, 1, dc_convnone, NULL) != DC_LOGIN_ERR_SUCCESS) {
715 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
718 tag = dc_queuecmd(NULL, NULL, L"hup", NULL);
719 if((resp = dc_gettaggedrespsync(tag)) != NULL) {
720 if(resp->code != 200)
721 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
727 void cb_cfw_save_activate(GtkWidget *widget, gpointer uudata)
731 if((cv = cfwvalid()) != NULL) {
732 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, cv->vld->invmsg, cv->rname);
739 void cb_cfw_quit_activate(GtkWidget *widget, gpointer uudata)
743 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)
750 int main(int argc, char **argv)
755 setlocale(LC_ALL, "");
756 bindtextdomain(PACKAGE, LOCALEDIR);
761 gtk_init(&argc, &argv);
763 while((c = getopt(argc, argv, "haw")) != -1) {
772 printf("usage: dolconf [-haw]\n");
773 printf("\t-h\tDisplay this help message\n");
774 printf("\t-a\tGo directly to the assistant\n");
775 printf("\t-w\tGo directly to the standard window\n");
778 fprintf(stderr, "usage: dolconf [-haw]\n");
785 shares = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
786 gtk_tree_view_set_model(GTK_TREE_VIEW(ast_sharelist), GTK_TREE_MODEL(shares));
787 gtk_tree_view_set_model(GTK_TREE_VIEW(cfw_sharelist), GTK_TREE_MODEL(shares));
790 if(getenv("HOME") != NULL) {
791 cfname = sprintf2("%s/.doldacond.conf", getenv("HOME"));
793 if((pwd = getpwuid(getuid())) != NULL)
794 cfname = sprintf2("%s/.doldacond.conf", pwd->pw_dir);
797 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
801 ex = !access(cfname, F_OK);
804 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)
813 if(ex && (state == 0)) {
814 if(readconfig() == 1) {
815 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)
822 gtk_window_set_default_size(GTK_WINDOW(cfw_wnd), 500, 350);
823 gtk_widget_show(cfw_wnd);
825 rootwnd = GTK_WINDOW(cfw_wnd);
827 gtk_widget_hide(cfw_wnd);
829 } else if(state == 1) {
830 gtk_window_set_default_size(GTK_WINDOW(ast_wnd), 500, 350);
831 gtk_widget_show(ast_wnd);
832 rootwnd = GTK_WINDOW(ast_wnd);
834 gtk_widget_hide(ast_wnd);
837 } else if(state == 2) {
838 for(i = 3; i < FD_SETSIZE; i++)
840 execlp("dolcon", "dolcon", NULL);
844 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal error (Unknown state)"));
851 #include "dolconf-assistant.gtk"
852 #include "dolconf-wnd.gtk"