$ git clone http://tcclient.ion.nu/tc_client.git
commit f82c4baf872b185a62c6090c80f506d2a7de2cb0
Author: Alicia <...>
Date:   Wed Dec 16 22:21:09 2015 +0100

    tc_client-gtk: added workarounds for the camera code (with the platform-agnostic "Image" camera) to work on windows.

diff --git a/ChangeLog b/ChangeLog
index 4c55abe..779e065 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
 Reimplemented announcement of moderators.
 Reimplemented announcement of people cammed up when joining.
 libcamera: added support for a virtual "Image" camera.
+tc_client-gtk: added workarounds for the camera code (with the platform-agnostic "Image" camera) to work on windows.
 0.36:
 Implemented /whois <nick/ID> to check someone's username.
 Changed the /whois output to be more human-readable in cases where the user isn't logged in.
diff --git a/Makefile b/Makefile
index d93b3f7..c3d8a8c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
 VERSION=0.37pre
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
-LIBS=-g3 $(shell curl-config --libs)
+LDFLAGS=-g3
 PREFIX=/usr/local
+CURL_LIBS=$(shell curl-config --libs)
 ifneq ($(wildcard config.mk),)
   include config.mk
 endif
@@ -30,9 +31,14 @@ ifdef SWSCALE_LIBS
   endif
   ifneq ($(findstring MINGW,$(shell uname -s)),)
     LDFLAGS+=-mwindows
-    windowstargets: camviewer tc_client-gtk
+    windowstargets: camviewer tc_client-gtk tc_client-gtk-camthread
  @echo
- @echo 'To build the core (tc_client.exe), enter this directory from cygwin and type make'
+ @echo 'To build the core (tc_client.exe), enter this directory from cygwin (or MSYS2 non-MinGW shell) and type make'
+  endif
+  ifneq ($(findstring MSYS,$(shell uname -s)),)
+    msystargets: tc_client
+ @echo
+ @echo 'To build the gtk+ GUI, enter this directory from a MinGW shell and type make'
   endif
   ifdef LIBV4L2_LIBS
     CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
@@ -59,7 +65,7 @@ CFLAGS+=-DPREFIX=\"$(PREFIX)\" -DVERSION=\"$(VERSION)\"
 INSTALLDEPS=tc_client
 
 tc_client: $(OBJ)
- $(CC) $(LDFLAGS) $^ $(LIBS) -o $@
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(CURL_LIBS) -o $@
 # Make sure client.o gets rebuilt if we change the version number in the Makefile
 client.o: Makefile
 
@@ -80,6 +86,23 @@ cursedchat: $(CURSEDCHAT_OBJ)
 tc_client-gtk: $(TC_CLIENT_GTK_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) -o $@
 
+# Workaround for windows' lack of fork() and inflexibility of having or not having a terminal
+utilities/gtk/camthread.gen.c: utilities/gtk/media.c
+ sed -n -e '/^ *#/p' utilities/gtk/media.c > $@
+ echo 'int main(int argc, char** argv){' >> $@
+ echo 'avcodec_register_all();' >> $@
+ echo 'AVCodec* vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);' >> $@
+ echo 'cam_img_filepicker=camselect_file;' >> $@
+ echo 'gtk_init(&argc, &argv);' >> $@
+ echo 'int campipe[]={0,1};' >> $@
+ echo 'setmode(1, O_BINARY);' >> $@
+ echo 'CAM* cam=cam_open(argv[1]);' >> $@
+ echo 'unsigned int delay=atoi(argv[2]);' >> $@
+ sed -n -e '/if(!camproc)$$/,/^  }/p' utilities/gtk/media.c | sed -e '1,3d' >> $@
+ sed -n -e '/ camselect_file/,/^}/p' utilities/gtk/media.c >> $@
+tc_client-gtk-camthread: utilities/gtk/camthread.gen.o utilities/compat.o libcamera.a
+ $(CC) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) -o $@
+
 libcamera.a: $(LIBCAMERA_OBJ)
  $(AR) cru $@ $^
  $(RANLIB) $@
diff --git a/configure b/configure
index 7d21e90..fa7ca24 100755
--- a/configure
+++ b/configure
@@ -13,15 +13,15 @@ done
 
 printf 'Checking for a C compiler... '
 if [ "x$CC" = "x" -a "x$host" != "x" ]; then
-  if which "${host}-cc" > /dev/null 2> /dev/null; then CC="${host}-cc"
-  elif which "${host}-gcc" > /dev/null 2> /dev/null; then CC="${host}-gcc"
+  if which "${host}-gcc" > /dev/null 2> /dev/null; then CC="${host}-gcc"
   elif which "${host}-clang" > /dev/null 2> /dev/null; then CC="${host}-clang"
+  elif which "${host}-cc" > /dev/null 2> /dev/null; then CC="${host}-cc"
   fi
 fi
 if [ "x$CC" = "x" ]; then
-  if which cc > /dev/null 2> /dev/null; then CC=cc
-  elif which gcc > /dev/null 2> /dev/null; then CC=gcc
+  if which gcc > /dev/null 2> /dev/null; then CC=gcc
   elif which clang > /dev/null 2> /dev/null; then CC=clang
+  elif which cc > /dev/null 2> /dev/null; then CC=cc
   fi
 fi
 if [ "x$CC" = "x" ]; then echo "None found"; exit 1; fi
diff --git a/utilities/compat.h b/utilities/compat.h
index eb1503f..2788686 100644
--- a/utilities/compat.h
+++ b/utilities/compat.h
@@ -23,6 +23,7 @@ extern size_t dprintf(int fd, const char* fmt, ...);
 #ifdef _WIN32
   #define prctl(...)
   #define wait(x)
+  #define mkdir(x,y) mkdir(x)
 #endif
 #if GLIB_MAJOR_VERSION<2 || (GLIB_MAJOR_VERSION==2 && GLIB_MINOR_VERSION<2)
   #define g_io_channel_read_chars(a,b,c,d,e) g_io_channel_read(a,b,c,d)
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 3636ea8..d410d2f 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -814,19 +814,6 @@ void startsession(GtkButton* button, void* x)
   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")));
 #ifdef _WIN32
-  HANDLE h_tc_client0, h_tc_client1;
-  CreatePipe(&h_tc_client0, &h_tc_client1, &sa, 0);
-  HANDLE h_tc_client_in0, h_tc_client_in1;
-  CreatePipe(&h_tc_client_in0, &h_tc_client_in1, &sa, 0);
-  tc_client[0]=_open_osfhandle(h_tc_client0, _O_RDONLY);
-  tc_client[1]=_open_osfhandle(h_tc_client1, _O_WRONLY);
-  tc_client_in[0]=_open_osfhandle(h_tc_client_in0, _O_RDONLY);
-  tc_client_in[1]=_open_osfhandle(h_tc_client_in1, _O_WRONLY);
-  STARTUPINFO startup;
-  GetStartupInfo(&startup);
-  startup.dwFlags|=STARTF_USESTDHANDLES;
-  startup.hStdInput=h_tc_client_in0;
-  startup.hStdOutput=h_tc_client1;
   char cmd[strlen("./tc_client -u    0")+strlen(acc_user)+strlen(channel)+strlen(nick)+strlen(chanpass)];
   strcpy(cmd, "./tc_client ");
   if(acc_user[0])
@@ -841,7 +828,7 @@ void startsession(GtkButton* button, void* x)
   strcat(cmd, " ");
   strcat(cmd, chanpass);
   strcat(cmd, " ");
-  CreateProcess(0, cmd, 0, 0, 1, DETACHED_PROCESS, 0, 0, &startup, &coreprocess);
+  w32_runcmdpipes(cmd, tc_client_in, tc_client, coreprocess);
 #else
   pipe(tc_client);
   pipe(tc_client_in);
@@ -1055,6 +1042,10 @@ int main(int argc, char** argv)
   {
     TerminateProcess(coreprocess.hProcess, 0);
   }
+  if(camproc)
+  {
+    TerminateProcess(camproc, 0);
+  }
 #endif
   camera_cleanup();
 #ifdef HAVE_AVRESAMPLE
diff --git a/utilities/gtk/compat.h b/utilities/gtk/compat.h
index 4e9c212..583edb7 100644
--- a/utilities/gtk/compat.h
+++ b/utilities/gtk/compat.h
@@ -16,13 +16,38 @@
 */
 #ifdef _WIN32
 #include <wtypes.h>
+#include <fcntl.h>
 extern SECURITY_ATTRIBUTES sa;
+#define kill(pid, x) TerminateProcess(pid, 0)
 #define w32_runcmd(cmd) \
   { \
     char* arg=strchr(cmd,' '); \
     if(arg){arg[0]=0; arg=&arg[1];} \
     ShellExecute(0, "open", cmd, arg, 0, SW_SHOWNORMAL); \
   }
+#define w32_runcmdpipes(cmd, pipein, pipeout, procinfo) \
+  { \
+    HANDLE h_pipe_in0, h_pipe_in1; \
+    if(pipein) \
+    { \
+      CreatePipe(&h_pipe_in0, &h_pipe_in1, &sa, 0); \
+      pipein[0]=_open_osfhandle(h_pipe_in0, _O_RDONLY); \
+      pipein[1]=_open_osfhandle(h_pipe_in1, _O_WRONLY); \
+    } \
+    HANDLE h_pipe_out0, h_pipe_out1; \
+    if(pipeout) \
+    { \
+      CreatePipe(&h_pipe_out0, &h_pipe_out1, &sa, 0); \
+      pipeout[0]=_open_osfhandle(h_pipe_out0, _O_RDONLY); \
+      pipeout[1]=_open_osfhandle(h_pipe_out1, _O_WRONLY); \
+    } \
+    STARTUPINFO startup; \
+    GetStartupInfo(&startup); \
+    startup.dwFlags|=STARTF_USESTDHANDLES; \
+    if(pipein){startup.hStdInput=h_pipe_in0;} \
+    if(pipeout){startup.hStdOutput=h_pipe_out1;} \
+    CreateProcess(0, cmd, 0, 0, 1, DETACHED_PROCESS, 0, 0, &startup, &procinfo); \
+  }
 #endif
 #if GTK_MAJOR_VERSION<3
   #define GTK_ORIENTATION_HORIZONTAL 0
diff --git a/utilities/gtk/logging.c b/utilities/gtk/logging.c
index f8e26f9..d9bcbb2 100644
--- a/utilities/gtk/logging.c
+++ b/utilities/gtk/logging.c
@@ -21,6 +21,7 @@
 #include <sys/stat.h>
 #include "config.h"
 #include "../stringutils.h"
+#include "../compat.h"
 
 struct logfile
 {
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index d254445..a804437 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -28,14 +28,18 @@
   #include <libavcore/imgutils.h>
 #endif
 #include "../libcamera/camera.h"
-#include "../compat.h"
 #include "compat.h"
+#include "../compat.h"
 #include "gui.h"
 #include "media.h"
 struct camera campreview;
 struct camera* cams=0;
 unsigned int camcount=0;
-pid_t camproc=0;
+#ifdef _WIN32
+  PROCESS_INFORMATION camprocess={.hProcess=0};
+#else
+  pid_t camproc=0;
+#endif
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 // Experimental mixer, not sure if it really works
@@ -170,17 +174,20 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
     usleep(200000); // Give the previous process some time to shut down
   }
   // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
-  CAM* cam=cam_open(name); // Opening here in case of GUI callbacks
   int campipe[2];
+#ifndef _WIN32
+  CAM* cam=cam_open(name); // Opening here in case of GUI callbacks
   pipe(campipe);
   camproc=fork();
   if(!camproc)
   {
     close(campipe[0]);
     if(!cam){_exit(1);}
+#ifndef _WIN32
     prctl(PR_SET_PDEATHSIG, SIGHUP);
     camthread_delay=&delay;
     signal(SIGUSR1, camthread_resetdelay);
+#endif
     // Set up camera
     AVCodecContext* ctx=avcodec_alloc_context3(vencoder);
     ctx->width=320;
@@ -235,6 +242,11 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
   }
   if(cam){cam_close(cam);} // Leave the cam to the child process
   close(campipe[1]);
+#else
+  char cmd[snprintf(0,0, "./tc_client-gtk-camthread %s %u", name, delay)+1];
+  sprintf(cmd, "./tc_client-gtk-camthread %s %u", name, delay);
+  w32_runcmdpipes(cmd, ((int*)0), campipe, camprocess);
+#endif
   GIOChannel* channel=g_io_channel_unix_new(campipe[0]);
   g_io_channel_set_encoding(channel, 0, 0);
   return channel;
@@ -263,8 +275,15 @@ void camselect_accept(GtkWidget* widget, AVCodec* vencoder)
 {
   GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "camselection"));
   gtk_widget_hide(window);
+#ifndef _WIN32
   // Tell the camthread to reset the delay to 500000 as a workaround for a quirk in the flash client
   kill(camproc, SIGUSR1);
+#else
+  // For platforms without proper signals, resort to restarting the camthread with the new delay
+  GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
+  GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), vencoder, 500000);
+  cameventsource=g_io_add_watch(channel, G_IO_IN, (void*)&handledata, 0);
+#endif
   dprintf(tc_client_in[1], "/camup\n");
 }
 
@@ -282,7 +301,11 @@ void camselect_file_preview(GtkFileChooser* dialog, gpointer data)
 const char* camselect_file(void)
 {
   GtkWidget* preview=gtk_image_new();
+#ifndef _WIN32
   GtkWindow* window=GTK_WINDOW(gtk_builder_get_object(gui, "camselection"));
+#else
+  GtkWindow* window=0;
+#endif
   GtkWidget* dialog=gtk_file_chooser_dialog_new("Open Image", window, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, (char*)0);
   GtkFileFilter* filter=gtk_file_filter_new();
   gtk_file_filter_add_pixbuf_formats(filter);
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 8b2db74..e94bb30 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -31,7 +31,12 @@ struct camera
 extern struct camera campreview;
 extern struct camera* cams;
 extern unsigned int camcount;
-extern pid_t camproc;
+#ifdef _WIN32
+  extern PROCESS_INFORMATION camprocess;
+  #define camproc camprocess.hProcess
+#else
+  extern pid_t camproc;
+#endif
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 extern void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount);