$ git clone http://tcclient.ion.nu/tc_client.git
commit a905b2c254b562b59cdac56484bda1797129a576
Author: Alicia <...>
Date:   Sun May 31 23:36:07 2015 +0200

    tc_client-gtk: redesigned the startup window to better support frequenting multiple channels.

diff --git a/ChangeLog b/ChangeLog
index 19ecf4b..4563189 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,6 @@
 0.33:
 modbot: fixed finding the duration of videos longer than 59 minutes (conversion from hh:mm:ss format to seconds)
+tc_client-gtk: redesigned the startup window to better support frequenting multiple channels.
 0.32:
 Added an 'install' make target, adjusted utilities to run tc_client from PATH unless they were run from the build directory (i.e. './<executable>')
 Provide feedback on the /ban command.
diff --git a/gtkgui.glade b/gtkgui.glade
index 8a12566..1e20f07 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -2,6 +2,193 @@
 <!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.0"/>
+  <object class="GtkWindow" id="channelconfig">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">tc_client</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="box6">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkLabel" id="label6">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="label" translatable="yes">Nickname:</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="cc_nick">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label7">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="label" translatable="yes">Channel:</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="cc_channel">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label16">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="label" translatable="yes">Channel password (if any):</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="cc_password">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="input_purpose">password</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">6</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkExpander" id="expander1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <object class="GtkBox" id="box7">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Username:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="acc_username">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Password:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="acc_password">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="visibility">False</property>
+                    <property name="input_purpose">password</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child type="label">
+              <object class="GtkLabel" id="label5">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Account</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">7</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cc_savebutton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">9</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cc_delete">
+            <property name="label" translatable="yes">Delete</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">10</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkWindow" id="channelpasswordwindow">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">Channel password?</property>
@@ -39,6 +226,72 @@
       </object>
     </child>
   </object>
+  <object class="GtkImage" id="image1">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-connect</property>
+  </object>
+  <object class="GtkImage" id="image2">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-add</property>
+  </object>
+  <object class="GtkWindow" id="startwindow">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">tc_client</property>
+    <property name="default_width">200</property>
+    <property name="default_height">200</property>
+    <signal name="destroy" handler="gtk_main_quit" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="startbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkMenuBar" id="menubar1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkMenuItem" id="menuitem2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Channel</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menu2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkImageMenuItem" id="start_menu_add">
+                        <property name="label" translatable="yes">Add</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image2</property>
+                        <property name="use_stock">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="start_menu_connect">
+                        <property name="label" translatable="yes">Connect without adding</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image1</property>
+                        <property name="use_stock">False</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkWindow" id="main">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">tc_client</property>
@@ -566,183 +819,4 @@
       </object>
     </child>
   </object>
-  <object class="GtkWindow" id="startwindow">
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">tc_client</property>
-    <property name="resizable">False</property>
-    <signal name="destroy" handler="gtk_main_quit" swapped="no"/>
-    <child>
-      <object class="GtkBox" id="box6">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkExpander" id="expander1">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <child>
-              <object class="GtkBox" id="box7">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkLabel" id="label8">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">Username:</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="acc_username">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label9">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">Password:</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="acc_password">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="visibility">False</property>
-                    <property name="input_purpose">password</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkCheckButton" id="start_rememberacc">
-                    <property name="label" translatable="yes">Remember account</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">False</property>
-                    <property name="xalign">0</property>
-                    <property name="draw_indicator">True</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">4</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-            <child type="label">
-              <object class="GtkLabel" id="label5">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Account</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkLabel" id="label6">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="halign">start</property>
-            <property name="label" translatable="yes">Nickname:</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="start_nick">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkLabel" id="label7">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="halign">start</property>
-            <property name="label" translatable="yes">Channel:</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">3</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="start_channel">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">4</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkCheckButton" id="start_rememberchan">
-            <property name="label" translatable="yes">Remember nickname and channel</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">False</property>
-            <property name="xalign">0</property>
-            <property name="draw_indicator">True</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">5</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkButton" id="connectbutton">
-            <property name="label" translatable="yes">Connect</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">6</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
 </interface>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 9522c9c..c5a7514 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -68,8 +68,8 @@ struct viddata
 #endif
   GtkTextBuffer* buffer; // TODO: struct buffer array, for PMs
   GtkAdjustment* scroll;
-  GtkBuilder* gui;
 };
+struct viddata* data;
 
 int tc_client[2];
 int tc_client_in[2];
@@ -136,9 +136,12 @@ void printchat_color(struct viddata* data, const char* text, const char* color,
   int startnum=gtk_text_iter_get_offset(&end);
   gtk_text_buffer_insert(data->buffer, &end, text, -1);
   // Set color if there was one
-  GtkTextIter start;
-  gtk_text_buffer_get_iter_at_offset(data->buffer, &start, startnum+offset);
-  gtk_text_buffer_apply_tag_by_name(data->buffer, color, &start, &end);
+  if(color)
+  {
+    GtkTextIter start;
+    gtk_text_buffer_get_iter_at_offset(data->buffer, &start, startnum+offset);
+    gtk_text_buffer_apply_tag_by_name(data->buffer, color, &start, &end);
+  }
   if(bottom){autoscroll_after(data->scroll);}
 }
 
@@ -146,7 +149,6 @@ char buf[1024];
 gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
 {
   int fd=g_io_channel_unix_get_fd(iochannel);
-  struct viddata* data=datap;
   unsigned int i;
   for(i=0; i<1023; ++i)
   {
@@ -191,8 +193,8 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   }
   if(!strcmp(buf, "Password required"))
   {
-    gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "main")));
-    gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(data->gui, "channelpasswordwindow")));
+    gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
+    gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordwindow")));
     return 1;
   }
   if(buf[0]=='/') // For the /help text
@@ -706,16 +708,39 @@ void sendmessage(GtkEntry* entry, struct viddata* data)
   gtk_entry_set_text(entry, "");
 }
 
-void startsession(GtkButton* button, struct viddata* data)
+void startsession(GtkButton* button, void* x)
 {
-  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "startwindow")));
-  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "channelpasswordwindow")));
-  gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(data->gui, "main")));
-  const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_nick")));
-  channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_channel")));
-  const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "channelpassword")));
-  const char* acc_user=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_username")));
-  const char* acc_pass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_password")));
+  if(x!=(void*)-1)
+  {
+    // Populate the quick-connect fields with saved data
+    int channel_id=(int)(intptr_t)x;
+    char buf[256];
+    sprintf(buf, "channel%i_nick", channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_name", channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_password", channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_accuser", channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_accpass", channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")), config_get_str(buf));
+    // TODO: if account password is empty, open a password entry window similar to the channel password one
+  }
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "startwindow")));
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelconfig")));
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordwindow")));
+  gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
+  const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")));
+  channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")));
+  const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "channelpassword")));
+  if(!chanpass[0]){chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")));}
+  const char* acc_user=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")));
+  const char* acc_pass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")));
   pipe(tc_client);
   pipe(tc_client_in);
   if(!fork())
@@ -737,67 +762,36 @@ void startsession(GtkButton* button, struct viddata* data)
   GIOChannel* tcchannel=g_io_channel_unix_new(tc_client[0]);
   g_io_channel_set_encoding(tcchannel, 0, 0);
   g_io_add_watch(tcchannel, G_IO_IN, handledata, data);
-  // Remember, if asked to
-  char save=0;
-  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(data->gui, "start_rememberchan"))))
-  {
-    config_set("remember_nick", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_nick"))));
-    config_set("remember_chan", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_channel"))));
-    config_set("remember_chan_nick", "True");
-    save=1;
-  }
-  else if(config_get_bool("remember_chan_nick")) // Remove previously remembered info
-  {
-    config_set("remember_nick", "");
-    config_set("remember_chan", "");
-    config_set("remember_chan_nick", "False");
-    save=1;
-  }
-  // Same for account
-  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(data->gui, "start_rememberacc"))))
-  {
-    config_set("remember_username", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_username"))));
-    config_set("remember_password", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_password"))));
-    config_set("remember_acc", "True");
-    save=1;
-  }
-  else if(config_get_bool("remember_acc")) // Remove previously remembered info
-  {
-    config_set("remember_username", "");
-    config_set("remember_password", "");
-    config_set("remember_acc", "False");
-    save=1;
-  }
-  if(save){config_save();}
 }
 
 int main(int argc, char** argv)
 {
   if(!strncmp(argv[0], "./", 2)){frombuild=1;}
-  struct viddata data={0,0,0,0,0};
+  struct viddata datax={0,0,0,0,0};
+  data=&datax;
   avcodec_register_all();
-  data.vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
-  data.adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
+  data->vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
+  data->adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
   signal(SIGCHLD, SIG_IGN);
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
   #ifdef HAVE_AVRESAMPLE
-  data.resamplectx=avresample_alloc_context();
-  av_opt_set_int(data.resamplectx, "in_channel_layout", AV_CH_FRONT_CENTER, 0);
-  av_opt_set_int(data.resamplectx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
+  data->resamplectx=avresample_alloc_context();
+  av_opt_set_int(data->resamplectx, "in_channel_layout", AV_CH_FRONT_CENTER, 0);
+  av_opt_set_int(data->resamplectx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
   // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
-  av_opt_set_int(data.resamplectx, "in_sample_rate", 11025, 0);
-  av_opt_set_int(data.resamplectx, "out_channel_layout", AV_CH_FRONT_CENTER, 0);
-  av_opt_set_int(data.resamplectx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
-  av_opt_set_int(data.resamplectx, "out_sample_rate", 22050, 0);
-  avresample_open(data.resamplectx);
+  av_opt_set_int(data->resamplectx, "in_sample_rate", 11025, 0);
+  av_opt_set_int(data->resamplectx, "out_channel_layout", AV_CH_FRONT_CENTER, 0);
+  av_opt_set_int(data->resamplectx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
+  av_opt_set_int(data->resamplectx, "out_sample_rate", 22050, 0);
+  avresample_open(data->resamplectx);
   #else
-  data.swrctx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0);
-  swr_init(data.swrctx);
+  data->swrctx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0);
+  swr_init(data->swrctx);
   #endif
   int audiopipe[2];
   pipe(audiopipe);
-  data.audiopipe=audiopipe[1];
+  data->audiopipe=audiopipe[1];
   if(!fork())
   {
     prctl(PR_SET_PDEATHSIG, SIGHUP);
@@ -809,7 +803,6 @@ int main(int argc, char** argv)
 #endif
 
   gtk_init(&argc, &argv);
-  GtkBuilder* gui;
   if(frombuild)
   {
     gui=gtk_builder_new_from_file("gtkgui.glade");
@@ -817,12 +810,11 @@ int main(int argc, char** argv)
     gui=gtk_builder_new_from_file(PREFIX "/share/tc_client/gtkgui.glade");
   }
   gtk_builder_connect_signals(gui, 0);
-  data.gui=gui;
 
 #ifdef HAVE_V4L2
   GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_camera"));
-  g_signal_connect(item, "toggled", G_CALLBACK(togglecam), &data);
-  data.vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
+  g_signal_connect(item, "toggled", G_CALLBACK(togglecam), data);
+  data->vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
 #else
   GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast"));
   gtk_widget_destroy(item);
@@ -831,13 +823,13 @@ int main(int argc, char** argv)
   item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_options_settings"));
   g_signal_connect(item, "activate", G_CALLBACK(showsettings), gui);
   
-  data.box=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
+  data->box=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
   userlistwidget=GTK_WIDGET(gtk_builder_get_object(gui, "userlistbox"));
   GtkWidget* chatview=GTK_WIDGET(gtk_builder_get_object(gui, "chatview"));
-  data.scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gtk_builder_get_object(gui, "chatscroll")));
+  data->scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gtk_builder_get_object(gui, "chatscroll")));
 
-  data.buffer=gtk_text_view_get_buffer(GTK_TEXT_VIEW(chatview));
-  #define colormap(code, color) gtk_text_buffer_create_tag(data.buffer, code, "foreground", color, (char*)0)
+  data->buffer=gtk_text_view_get_buffer(GTK_TEXT_VIEW(chatview));
+  #define colormap(code, color) gtk_text_buffer_create_tag(data->buffer, code, "foreground", color, (char*)0)
   colormap("[31", "#821615");
   colormap("[31;1", "#c53332");
   colormap("[33", "#a08f23");
@@ -856,11 +848,11 @@ int main(int argc, char** argv)
   //colormap("[35;1", "#b9807f");
 
   GtkWidget* panes=GTK_WIDGET(gtk_builder_get_object(gui, "vpaned"));
-  g_signal_connect(panes, "notify::position", G_CALLBACK(handleresizepane), &data);
+  g_signal_connect(panes, "notify::position", G_CALLBACK(handleresizepane), data);
 
   GtkWidget* inputfield=GTK_WIDGET(gtk_builder_get_object(gui, "inputfield"));
-  g_signal_connect(inputfield, "activate", G_CALLBACK(sendmessage), &data);
-  g_signal_connect(inputfield, "key-press-event", G_CALLBACK(inputkeys), &data);
+  g_signal_connect(inputfield, "activate", G_CALLBACK(sendmessage), data);
+  g_signal_connect(inputfield, "key-press-event", G_CALLBACK(inputkeys), data);
 
   config_load();
   // Sound
@@ -876,29 +868,46 @@ int main(int argc, char** argv)
   g_signal_connect(option, "toggled", G_CALLBACK(toggle_youtubecmd), gui);
 
   GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "main"));
-  g_signal_connect(window, "configure-event", G_CALLBACK(handleresize), &data);
+  g_signal_connect(window, "configure-event", G_CALLBACK(handleresize), data);
 
   // Start window and channel password window signals
-  GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "connectbutton"));
-  g_signal_connect(button, "clicked", G_CALLBACK(startsession), &data);
-  button=GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordbutton"));
-  g_signal_connect(button, "clicked", G_CALLBACK(startsession), &data);
+  GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordbutton"));
+  g_signal_connect(button, "clicked", G_CALLBACK(startsession), (void*)-1); // &data);
   button=GTK_WIDGET(gtk_builder_get_object(gui, "channelpassword"));
-  g_signal_connect(button, "activate", G_CALLBACK(startsession), &data);
+  g_signal_connect(button, "activate", G_CALLBACK(startsession), (void*)-1); // &data);
   GtkWidget* startwindow=GTK_WIDGET(gtk_builder_get_object(gui, "startwindow"));
-  // Set channel and nick from last session
-  if(config_get_bool("remember_chan_nick"))
-  {
-    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "start_nick")), config_get_str("remember_nick"));
-    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "start_channel")), config_get_str("remember_chan"));
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "start_rememberchan")), 1);
-  }
-  // Set username and password from last session
-  if(config_get_bool("remember_acc"))
-  {
-    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")), config_get_str("remember_username"));
-    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")), config_get_str("remember_password"));
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "start_rememberacc")), 1);
+  // Connect signal for quick connect
+  item=GTK_WIDGET(gtk_builder_get_object(gui, "start_menu_connect"));
+  struct channelopts cc_connect={-1,0};
+  g_signal_connect(item, "activate", G_CALLBACK(channeldialog), &cc_connect);
+  // Connect signal for the add option
+  item=GTK_WIDGET(gtk_builder_get_object(gui, "start_menu_add"));
+  struct channelopts cc_add={-1,1};
+  g_signal_connect(item, "activate", G_CALLBACK(channeldialog), &cc_add);
+  // Populate saved channels
+  GtkWidget* startbox=GTK_WIDGET(gtk_builder_get_object(gui, "startbox"));
+  int channelcount=config_get_int("channelcount");
+  char buf[256];
+  int i;
+  for(i=0; i<channelcount; ++i)
+  {
+    GtkWidget* box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    #ifdef GTK_STYLE_CLASS_LINKED
+      GtkStyleContext* style=gtk_widget_get_style_context(box);
+      gtk_style_context_add_class(style, GTK_STYLE_CLASS_LINKED);
+    #endif
+    sprintf(buf, "channel%i_name", i);
+    const char* name=config_get_str(buf);
+    GtkWidget* connectbutton=gtk_button_new_with_label(name);
+    g_signal_connect(connectbutton, "clicked", G_CALLBACK(startsession), (void*)(intptr_t)i);
+    gtk_box_pack_start(GTK_BOX(box), connectbutton, 1, 1, 0);
+    GtkWidget* cfgbutton=gtk_button_new_from_icon_name("gtk-preferences", GTK_ICON_SIZE_BUTTON);
+    struct channelopts* opts=malloc(sizeof(struct channelopts));
+    opts->channel_id=i;
+    opts->save=1;
+    g_signal_connect(cfgbutton, "clicked", G_CALLBACK(channeldialog), opts);
+    gtk_box_pack_start(GTK_BOX(box), cfgbutton, 0, 0, 0);
+    gtk_box_pack_start(GTK_BOX(startbox), box, 0, 0, 2);
   }
   gtk_widget_show_all(startwindow);
 
@@ -906,9 +915,9 @@ int main(int argc, char** argv)
  
   camera_cleanup();
 #ifdef HAVE_AVRESAMPLE
-  avresample_free(&data.resamplectx);
+  avresample_free(&data->resamplectx);
 #elif defined(HAVE_SWRESAMPLE)
-  swr_free(&data.swrctx);
+  swr_free(&data->swrctx);
 #endif
   return 0;
 }
diff --git a/utilities/gtk/compat.h b/utilities/gtk/compat.h
index 88004d2..a0a5ee2 100644
--- a/utilities/gtk/compat.h
+++ b/utilities/gtk/compat.h
@@ -22,3 +22,6 @@
   extern int gtk_widget_get_allocated_height(GtkWidget* widget);
   extern GtkBuilder* gtk_builder_new_from_file(const char* filename);
 #endif
+#if GTK_MAJOR_VERSION<3 || GTK_MINOR_VERSION<10
+  #define gtk_button_new_from_icon_name(name, size) gtk_button_new_from_stock(name)
+#endif
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index aa7f8df..ded7028 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -14,10 +14,16 @@
     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
+#include <stdlib.h>
+#include <string.h>
 #include <gtk/gtk.h>
 #include "gui.h"
 #include "config.h"
 #include "logging.h"
+#include "compat.h"
+
+extern void startsession(GtkButton* button, void* x);
+GtkBuilder* gui;
 
 char autoscroll_before(GtkAdjustment* scroll)
 {
@@ -109,3 +115,184 @@ void toggle_youtubecmd(GtkToggleButton* button, GtkBuilder* gui)
   GtkWidget* field=GTK_WIDGET(gtk_builder_get_object(gui, "youtubecmd"));
   gtk_widget_set_sensitive(field, gtk_toggle_button_get_active(button));
 }
+
+void savechannel(GtkButton* button, void* x)
+{
+  int channelid;
+  if(x==(void*)-1)
+  {
+    channelid=config_get_int("channelcount");
+    config_set_int("channelcount", channelid+1);
+  }else{
+    channelid=(int)(intptr_t)x;
+  }
+  char buf[256];
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelconfig")));
+  const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")));
+  sprintf(buf, "channel%i_nick", channelid);
+  config_set(buf, nick);
+
+  const char* channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")));
+  sprintf(buf, "channel%i_name", channelid);
+  config_set(buf, channel);
+
+  const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")));
+  sprintf(buf, "channel%i_password", channelid);
+  config_set(buf, chanpass);
+
+  const char* acc_user=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")));
+  sprintf(buf, "channel%i_accuser", channelid);
+  config_set(buf, acc_user);
+
+  const char* acc_pass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")));
+  sprintf(buf, "channel%i_accpass", channelid);
+  config_set(buf, acc_pass);
+  config_save();
+
+  if(x==(void*)-1)
+  {
+    GtkWidget* startbox=GTK_WIDGET(gtk_builder_get_object(gui, "startbox"));
+    GtkWidget* box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    #ifdef GTK_STYLE_CLASS_LINKED
+      GtkStyleContext* style=gtk_widget_get_style_context(box);
+      gtk_style_context_add_class(style, GTK_STYLE_CLASS_LINKED);
+    #endif
+    GtkWidget* connectbutton=gtk_button_new_with_label(channel);
+    g_signal_connect(connectbutton, "clicked", G_CALLBACK(startsession), (void*)(intptr_t)channelid);
+    gtk_box_pack_start(GTK_BOX(box), connectbutton, 1, 1, 0);
+    GtkWidget* cfgbutton=gtk_button_new_from_icon_name("gtk-preferences", GTK_ICON_SIZE_BUTTON);
+    struct channelopts* opts=malloc(sizeof(struct channelopts));
+    opts->channel_id=channelid;
+    opts->save=1;
+    g_signal_connect(cfgbutton, "clicked", G_CALLBACK(channeldialog), opts);
+    gtk_box_pack_start(GTK_BOX(box), cfgbutton, 0, 0, 0);
+    gtk_box_pack_start(GTK_BOX(startbox), box, 0, 0, 2);
+    gtk_widget_show_all(box);
+  }
+}
+
+char deletechannel_found;
+int deletechannel_id;
+void deletechannel_check(GtkWidget* widget, const char* name)
+{
+  if(!GTK_IS_CONTAINER(widget)){return;}
+  GList* list=gtk_container_get_children(GTK_CONTAINER(widget));
+  if(GTK_IS_BUTTON(list->data))
+  {
+    if(!strcmp(gtk_button_get_label(GTK_BUTTON(list->data)), name))
+    {
+      deletechannel_found=1;
+      gtk_widget_destroy(widget);
+    }
+    else if(deletechannel_found)
+    {
+      // After the channel we deleted we have to increment the numbers (data for events) for subsequent buttons, first remove existing events
+      g_signal_handlers_disconnect_matched(list->data, G_SIGNAL_MATCH_FUNC, 0, 0, 0, startsession, 0);
+      g_signal_handlers_disconnect_matched(list->next->data, G_SIGNAL_MATCH_FUNC, 0, 0, 0, channeldialog, 0);
+      // Bind new events
+      g_signal_connect(list->data, "clicked", G_CALLBACK(startsession), (void*)(intptr_t)deletechannel_id);
+      struct channelopts* opts=malloc(sizeof(struct channelopts));
+      opts->channel_id=deletechannel_id;
+      opts->save=1;
+      g_signal_connect(list->next->data, "clicked", G_CALLBACK(channeldialog), opts);
+      ++deletechannel_id;
+    }
+  }
+  g_list_free(list);
+}
+
+void deletechannel(GtkButton* button, void* x)
+{
+  GtkContainer* startbox=GTK_CONTAINER(gtk_builder_get_object(gui, "startbox"));
+  int channelid=(int)(intptr_t)x;
+  char buf[256];
+  sprintf(buf, "channel%i_name", channelid);
+  // Delete the buttons from the startbox
+  deletechannel_found=0;
+  deletechannel_id=channelid;
+  gtk_container_foreach(startbox, (GtkCallback)deletechannel_check, (void*)config_get_str(buf));
+  GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "channelconfig"));
+  gtk_widget_hide(window);
+  // Delete from the configuration
+  char buf2[256];
+  int channelcount=config_get_int("channelcount")-1;
+  for(;channelid<channelcount; ++channelid)
+  {
+    #define move_channel_var(x) \
+    sprintf(buf, "channel%i_"x, channelid); \
+    sprintf(buf2, "channel%i_"x, channelid+1); \
+    config_set(buf, config_get_str(buf2));
+    move_channel_var("name");
+    move_channel_var("nick");
+    move_channel_var("password");
+    move_channel_var("accuser");
+    move_channel_var("accpass");
+  }
+  // Clear entries from the now unused channel ID
+  sprintf(buf, "channel%i_name", channelcount);
+  config_set(buf, "");
+  sprintf(buf, "channel%i_nick", channelcount);
+  config_set(buf, "");
+  sprintf(buf, "channel%i_password", channelcount);
+  config_set(buf, "");
+  sprintf(buf, "channel%i_accuser", channelcount);
+  config_set(buf, "");
+  sprintf(buf, "channel%i_accpass", channelcount);
+  config_set(buf, "");
+  config_set_int("channelcount", channelcount);
+  config_save();
+}
+
+void channeldialog(GtkButton* button, struct channelopts* opts)
+{
+  // If we're not going from an existing channel, clear the fields
+  if(opts->channel_id==-1)
+  {
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")), "");
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")), "");
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")), "");
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")), "");
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")), "");
+  }else{ // But if we are, populate the fields
+    char buf[256];
+    sprintf(buf, "channel%i_nick", opts->channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_name", opts->channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_password", opts->channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_accuser", opts->channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")), config_get_str(buf));
+
+    sprintf(buf, "channel%i_accpass", opts->channel_id);
+    gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")), config_get_str(buf));
+  }
+  GObject* obj=gtk_builder_get_object(gui, "cc_savebutton");
+  // Connect the save button either to startsession or savechannel depending on if we're adding/editing or quick-connecting
+  g_signal_handlers_disconnect_matched(obj, G_SIGNAL_MATCH_FUNC, 0, 0, 0, savechannel, 0);
+  g_signal_handlers_disconnect_matched(obj, G_SIGNAL_MATCH_FUNC, 0, 0, 0, startsession, 0);
+  if(opts->save)
+  {
+    gtk_button_set_label(GTK_BUTTON(obj), "Save");
+    g_signal_connect(obj, "clicked", G_CALLBACK(savechannel), (void*)(intptr_t)opts->channel_id);
+  }else{
+    gtk_button_set_label(GTK_BUTTON(obj), "Connect");
+    g_signal_connect(obj, "clicked", G_CALLBACK(startsession), (void*)-1);
+  }
+  obj=gtk_builder_get_object(gui, "channelconfig");
+  gtk_window_set_transient_for(GTK_WINDOW(obj), GTK_WINDOW(gtk_builder_get_object(gui, "startwindow")));
+  gtk_widget_show_all(GTK_WIDGET(obj));
+
+  obj=gtk_builder_get_object(gui, "cc_delete");
+  if(opts->channel_id==-1)
+  {
+    gtk_widget_hide(GTK_WIDGET(obj));
+  }else{
+    // Connect signal for channel delete button
+    g_signal_handlers_disconnect_matched(obj, G_SIGNAL_MATCH_FUNC, 0, 0, 0, deletechannel, 0);
+    g_signal_connect(obj, "clicked", G_CALLBACK(deletechannel), (void*)(intptr_t)opts->channel_id);
+  }
+}
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
index e4cfa68..971d680 100644
--- a/utilities/gtk/gui.h
+++ b/utilities/gtk/gui.h
@@ -16,6 +16,12 @@
 */
 #include <gtk/gtk.h>
 
+struct channelopts
+{
+  int channel_id; // for editing an existing channel, otherwise pass -1
+  char save;
+};
+
 extern char autoscroll_before(GtkAdjustment* scroll);
 extern void autoscroll_after(GtkAdjustment* scroll);
 extern void settings_reset(GtkBuilder* gui);
@@ -24,3 +30,7 @@ extern void savesettings(GtkButton* button, GtkBuilder* gui);
 extern void toggle_soundcmd(GtkToggleButton* button, GtkBuilder* gui);
 extern void toggle_logging(GtkToggleButton* button, GtkBuilder* gui);
 extern void toggle_youtubecmd(GtkToggleButton* button, GtkBuilder* gui);
+extern void deletechannel(GtkButton* button, void* x);
+extern void channeldialog(GtkButton* button, struct channelopts* opts);
+
+extern GtkBuilder* gui;