$ git clone http://tcclient.ion.nu/tc_client.git
commit 4e22a4ddc82a90027df37dd322cf11e0cf8c266d
Author: Alicia <...>
Date:   Wed May 24 20:32:21 2017 +0200

    tc_client-gtk: added a webkitview for solving captchas without an external browser.

diff --git a/ChangeLog b/ChangeLog
index bff8be4..a7aee0c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -22,6 +22,7 @@ tc_client-gtk: use /quit to guarantee a clean exit.
 tc_client-gtk: fixed segfault at exit by doing camera cleanup earlier.
 tc_client-gtk: made the automatic brightness postprocessing adjustment gradual.
 tc_client-gtk: use pulseaudio for incoming audio if it's available (preferred over libao) and display the volume indicator even if no audio playback library is available.
+tc_client-gtk: added a webkitview for solving captchas without an external browser.
 irchack: don't rely on connection ID.
 0.41.1:
 Use tinychat.com instead of apl.tinychat.com (works around SSL/TLS issues on windows)
diff --git a/Makefile b/Makefile
index 91131be..006b83c 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,9 @@ ifdef SWSCALE_LIBS
   UTILS+=camviewer tc_client-gtk
   CFLAGS+=$(GTK_CFLAGS) $(AVCODEC_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
   INSTALLDEPS+=tc_client-gtk gtkgui.glade
+  ifdef HAVE_WEBKITGTK
+    CFLAGS+=-DHAVE_WEBKITGTK=1 $(WEBKITGTK_CFLAGS)
+  endif
   ifdef HAVE_PULSE
     AUDIO_CFLAGS=-DHAVE_PULSEAUDIO=1 $(PULSE_CFLAGS)
     HAVE_AUDIOLIB=1
@@ -139,7 +142,7 @@ cursedchat: $(CURSEDCHAT_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
 
 tc_client-gtk: $(TC_CLIENT_GTK_OBJ) camplaceholder.gif modicon.png greenroomindicator.png
- $(CC) $(LDFLAGS) $(TC_CLIENT_GTK_OBJ) $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AVFORMAT_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) $(LIBX11_LIBS) $(PULSE_LIBS) -o $@
+ $(CC) $(LDFLAGS) $(TC_CLIENT_GTK_OBJ) $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AVFORMAT_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) $(LIBX11_LIBS) $(PULSE_LIBS) $(WEBKITGTK_LIBS) -o $@
 
 camplaceholder.gif: utilities/gtk/gencamplaceholder.sh utilities/gtk/camplaceholder.xcf utilities/gtk/spinnerdot.xcf
  utilities/gtk/gencamplaceholder.sh
diff --git a/configure b/configure
index 8ddf184..c3cf343 100755
--- a/configure
+++ b/configure
@@ -252,6 +252,9 @@ if testpkgconfig glib-2.0 GLIB; then
   echo 'CFLAGS+=$(GLIB_CFLAGS) -DHAVE_GLIB=1' >> config.mk
 fi
 
+testpkgconfig webkit2gtk-4.0 WEBKITGTK || \
+testpkgconfig webkit2gtk-3.0 WEBKITGTK
+
 printf 'Checking if we have a working poll()... '
 echo '#include <poll.h>' > polltest.c
 echo '#include <sys/socket.h>' >> polltest.c
diff --git a/tinychat.c b/tinychat.c
index 6d477e2..ae49299 100644
--- a/tinychat.c
+++ b/tinychat.c
@@ -117,7 +117,7 @@ static void getcaptcha(const char* channel)
       end[0]=0;
       printf("Captcha: http://tinychat.com/%s + javascript:void(ShowRecaptcha('%s'));\n", channel, token);
       fflush(stdout);
-      fgetc(stdin);
+      if(fgetc(stdin)==EOF){exit(0);}
     }
   }
   free(page);
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 635b3c4..bf213aa 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -50,6 +50,9 @@
 #endif
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
+#ifdef HAVE_WEBKITGTK
+#include <webkit2/webkit2.h>
+#endif
 #include "../compat.h"
 #include "../compat_av.h"
 #ifndef NO_PRCTL
@@ -148,6 +151,41 @@ void printchat(const char* text, const char* color, unsigned int offset, const c
   chatview_autoscroll(chatview);
 }
 
+#ifdef HAVE_WEBKITGTK
+static void removefrom(GtkWidget* widget, void* parent)
+{
+  gtk_container_remove(parent, widget);
+}
+#endif
+
+void captcha_done(void* x, void* y)
+{
+  GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "captcha"));
+  gtk_widget_hide(window);
+  gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
+  write(tc_client_in[1], "\n", 1);
+  if(greenroompipe_in[1]>-1){write(greenroompipe_in[1], "\n", 1);}
+#ifdef HAVE_WEBKITGTK // Get rid of the webkit view when we're done with it
+  gtk_container_foreach(GTK_CONTAINER(window), removefrom, window);
+}
+
+void captchajs(WebKitWebView* webview, WebKitLoadEvent event, gpointer data)
+{
+  // Hide unrelated parts of the page (tinychat-specific)
+  webkit_web_view_run_javascript(webview, "document.getElementById('footer').style.display='none'; document.getElementById('tinychat').style.display='none';", 0, 0, 0);
+  if(event!=WEBKIT_LOAD_FINISHED){return;}
+  g_signal_handlers_disconnect_by_data(webview, data);
+  // Add a note about loading the captcha (which is pretty slow)
+  webkit_web_view_run_javascript(webview, "var loaddiv=document.createElement('div'); loaddiv.appendChild(document.createTextNode('Loading captcha...')); loaddiv.style.background='rgb(255,255,255)'; document.body.appendChild(loaddiv); CaptchaSolvedSuccessfully=window.close;", 0, 0, 0);
+  const char* js=data;
+  // Put the javascript into the body tag's onload event, which is still slightly later than WEBKIT_LOAD_FINISHED
+  char buf[strlen("document.body.onload=function(){};0")+strlen(js)];
+  sprintf(buf, "document.body.onload=function(){%s};", js);
+  webkit_web_view_run_javascript(webview, buf, 0, 0, 0);
+  free(data);
+#endif
+}
+
 unsigned int cameventsource=0;
 gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
 {
@@ -268,10 +306,38 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
   if(!strncmp(buf, "Captcha: ", 9))
   {
     gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
-    gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "captcha")));
+    GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "captcha"));
+    gtk_widget_show_all(window);
+  #ifdef HAVE_WEBKITGTK
+    // Resize to fit the captcha and clear the window
+    gtk_window_resize(GTK_WINDOW(window), 450, 600);
+    gtk_container_foreach(GTK_CONTAINER(window), removefrom, window);
+    GtkWidget* box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    gtk_container_add(GTK_CONTAINER(window), box);
+    // Set up webkit webview
+    GtkWidget* webview=webkit_web_view_new();
+    g_signal_connect(webview, "close", G_CALLBACK(captcha_done), 0);
+    gtk_box_pack_start(GTK_BOX(box), webview, 1, 1, 0);
+    // Add a "Done" button, for skipping the captcha or if the captcha doesn't close the window itself
+    GtkWidget* button=gtk_button_new_with_label("Done");
+    g_signal_connect(button, "clicked", G_CALLBACK(captcha_done), 0);
+    gtk_box_pack_start(GTK_BOX(box), button, 0, 0, 0);
+    gtk_widget_show_all(box);
+    // Load the captcha page, and run the javascript if any was specified
+    char* js=strstr(&buf[9], " + javascript:");
+    if(js){js[0]=0;}
+    webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), &buf[9]);
+    if(js)
+    {
+      js=&js[14];
+      // Wait for it to load
+      g_signal_connect(webview, "load-changed", G_CALLBACK(captchajs), strdup(js));
+    }
+  #else
     char link[snprintf(0,0,"Captcha: <a href=\"%s\">%s</a>", &buf[9], &buf[9])+1];
     sprintf(link, "Captcha: <a href=\"%s\">%s</a>", &buf[9], &buf[9]);
     gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(gui, "captcha_link")), link);
+  #endif
     return 1;
   }
   // Start streams once we're properly connected
@@ -1005,14 +1071,6 @@ void startsession(GtkButton* button, void* x)
 #endif
 }
 
-void captcha_done(GtkWidget* button, void* x)
-{
-  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "captcha")));
-  gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
-  write(tc_client_in[1], "\n", 1);
-  if(greenroompipe_in[1]>-1){write(greenroompipe_in[1], "\n", 1);}
-}
-
 #ifndef _WIN32
 void justwait(int x){waitpid(-1, 0, WNOHANG);}
 #endif
diff --git a/utilities/gtk/main.h b/utilities/gtk/main.h
index 3624296..b60b426 100644
--- a/utilities/gtk/main.h
+++ b/utilities/gtk/main.h
@@ -1,6 +1,6 @@
 /*
     tc_client-gtk, a graphical user interface for tc_client
-    Copyright (C) 2015-2016  alicia@ion.nu
+    Copyright (C) 2015-2017  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -28,4 +28,4 @@ void togglemic(GtkCheckMenuItem* item, void* x);
 extern gboolean inputkeys(GtkWidget* widget, GdkEventKey* event, void* data);
 extern void sendmessage(GtkEntry* entry, void* x);
 extern void startsession(GtkButton* button, void* x);
-extern void captcha_done(GtkWidget* button, void* x);
+extern void captcha_done(void* x, void* y);