$ git clone http://tcclient.ion.nu/tc_client.git
commit d62ea903c69850ec532e8ae09ed972ff6c2c1238
Author: Alicia <...>
Date:   Sun Dec 18 20:16:15 2016 +0100

    tc_client-gtk: implemented broadcasting to greenroom while awaiting approval.

diff --git a/ChangeLog b/ChangeLog
index febc61c..08fcde4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -28,6 +28,7 @@ tc_client-gtk: added an option to hide join/quit/nickname notifications.
 tc_client-gtk: made the user list sorted.
 tc_client-gtk: added support for viewing and approving greenroom cameras.
 tc_client-gtk: added an option to show the greenroom menu even when you're not a moderator.
+tc_client-gtk: implemented broadcasting to greenroom while awaiting approval.
 dist/appimage.sh: fix audio in appimages by building ffmpeg with support for nellymoser and speex, and depending on the system's libao and libpulse instead of including it in the appimage.
 libcamera(escapi): handle failure to open camera more gracefully.
 irchack: pass along "<user> cammed up" notifications.
diff --git a/Makefile b/Makefile
index e667f06..dbd3239 100644
--- a/Makefile
+++ b/Makefile
@@ -132,7 +132,7 @@ camviewer: $(CAMVIEWER_OBJ)
 cursedchat: $(CURSEDCHAT_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
 
-tc_client-gtk: $(TC_CLIENT_GTK_OBJ) camplaceholder.gif modicon.png
+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 $@
 
 camplaceholder.gif: utilities/gtk/gencamplaceholder.sh utilities/gtk/camplaceholder.xcf utilities/gtk/spinnerdot.xcf
@@ -141,6 +141,9 @@ camplaceholder.gif: utilities/gtk/gencamplaceholder.sh utilities/gtk/camplacehol
 modicon.png: $(SRCDIR)utilities/gtk/modicon.xcf
  convert -background none $< -layers Merge -scale x20 $@
 
+greenroomindicator.png: $(SRCDIR)utilities/gtk/greenroomindicator.xcf
+ convert $< -layers Merge $@
+
 libcamera.a: $(LIBCAMERA_OBJ)
  $(AR) cru $@ $^
  $(RANLIB) $@
@@ -154,7 +157,7 @@ SOURCES+=utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/que
 SOURCES+=utilities/camviewer/camviewer.c
 SOURCES+=utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h
 SOURCES+=utilities/gtk/camviewer.c utilities/gtk/userlist.c utilities/gtk/media.c utilities/gtk/compat.c utilities/gtk/configfile.c utilities/gtk/gui.c utilities/gtk/logging.c utilities/gtk/postproc.c utilities/gtk/inputhistory.c utilities/gtk/playmedia.c utilities/gtk/greenroom.c utilities/gtk/main.h utilities/gtk/userlist.h utilities/gtk/media.h utilities/gtk/compat.h utilities/gtk/configfile.h utilities/gtk/gui.h utilities/gtk/logging.h utilities/gtk/postproc.h utilities/gtk/inputhistory.h utilities/gtk/playmedia.h utilities/gtk/greenroom.h gtkgui.glade
-SOURCES+=utilities/gtk/gencamplaceholder.sh utilities/gtk/camplaceholder.xcf utilities/gtk/spinnerdot.xcf utilities/gtk/modicon.xcf
+SOURCES+=utilities/gtk/gencamplaceholder.sh utilities/gtk/camplaceholder.xcf utilities/gtk/spinnerdot.xcf utilities/gtk/modicon.xcf utilities/gtk/greenroomindicator.xcf
 SOURCES+=utilities/compat.c utilities/compat.h utilities/list.c utilities/list.h utilities/stringutils.c utilities/stringutils.h utilities/compat_av.c utilities/compat_av.h
 SOURCES+=utilities/libcamera/camera.c utilities/libcamera/camera.h utilities/libcamera/camera_v4l2.c utilities/libcamera/camera_v4l2.h utilities/libcamera/camera_img.c utilities/libcamera/camera_img.h utilities/libcamera/camera_escapi.cpp utilities/libcamera/camera_escapi.h utilities/libcamera/camera_x11.c utilities/libcamera/camera_x11.h
 tarball:
@@ -170,6 +173,7 @@ ifdef SWSCALE_LIBS
  install -D gtkgui.glade "$(PREFIX)/share/tc_client/gtkgui.glade"
  install -D camplaceholder.gif "$(PREFIX)/share/tc_client/camplaceholder.gif"
  install -D modicon.png "$(PREFIX)/share/tc_client/modicon.png"
+ install -D greenroomindicator.png "$(PREFIX)/share/tc_client/greenroomindicator.png"
 endif
 endif
 endif
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index d42cbef..c91a67b 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -80,6 +80,7 @@ char hasgreenroom=0;
 char frombuild=0; // Running from the build directory
 #ifdef _WIN32
   PROCESS_INFORMATION coreprocess={.hProcess=0};
+  PROCESS_INFORMATION grprocess={.hProcess=0}; // Greenroom
 #endif
 
 void printchat(const char* text, const char* color, unsigned int offset, const char* pm)
@@ -284,6 +285,11 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
     }
     return 1;
   }
+  if(!strcmp(buf, "Authenticated to broadcast"))
+  {
+    greenroom_allowed();
+    return 1;
+  }
   if(!strcmp(buf, "Password required"))
   {
     gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
@@ -516,6 +522,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
   }
   if(!strcmp(buf, "Starting outgoing media stream"))
   {
+    if(camera_find("out")){return 1;} // In case we're just moving out of greenroom
     struct camera* cam=camera_new(nickname, "out", CAMFLAG_NONE);
     cam->vctx=avcodec_alloc_context3(data->vencoder);
     cam->vctx->pix_fmt=AV_PIX_FMT_YUV420P;
@@ -611,7 +618,8 @@ void togglecam(GtkCheckMenuItem* item, void* x)
     // TODO: if switching from cam+mic to just mic, send something to tell other clients to drop the last video frame and show a mic instead
     if(camera_find("out"))
     {
-      dprintf(tc_client_in[1], "/camdown\n");
+      int fd=((hasgreenroom && !greenroom_gotpass)?greenroompipe_in[1]:tc_client_in[1]);
+      dprintf(fd, "/camdown\n");
       camera_remove("out", 0); // Close our local display
     }
     return;
@@ -624,6 +632,7 @@ void togglemic(GtkCheckMenuItem* item, void* x)
 {
   static int micpipe[2];
   static int eventsource=0;
+  int fd=((hasgreenroom && !greenroom_gotpass)?greenroompipe_in[1]:tc_client_in[1]);
   if(!gtk_check_menu_item_get_active(item)) // Stop mic broadcast
   {
     gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalk")));
@@ -636,7 +645,7 @@ void togglemic(GtkCheckMenuItem* item, void* x)
     if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera")))){return;}
     if(camera_find("out"))
     {
-      dprintf(tc_client_in[1], "/camdown\n");
+      dprintf(fd, "/camdown\n");
       camera_remove("out", 0); // Close our local display
     }
     return;
@@ -658,7 +667,7 @@ void togglemic(GtkCheckMenuItem* item, void* x)
   // Start mic broadcast
   if(!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera"))))
   { // Only /camup if we're not already broadcasting video
-    dprintf(tc_client_in[1], "/camup\n");
+    dprintf(fd, "/camup\n");
   }
   pipe(micpipe);
   g_thread_new("audio_in", audiothread_in, &micpipe[1]);
@@ -1009,6 +1018,10 @@ int main(int argc, char** argv)
   {
     TerminateProcess(coreprocess.hProcess, 0);
   }
+  if(grprocess.hProcess)
+  {
+    TerminateProcess(grprocess.hProcess, 0);
+  }
 #endif
   camera_cleanup();
   return 0;
diff --git a/utilities/gtk/greenroom.c b/utilities/gtk/greenroom.c
index 754fa3c..0ccf434 100644
--- a/utilities/gtk/greenroom.c
+++ b/utilities/gtk/greenroom.c
@@ -38,11 +38,14 @@
 #include "configfile.h"
 #include "userlist.h"
 #include "greenroom.h"
+#ifdef _WIN32
+  #include <wtypes.h>
+  extern PROCESS_INFORMATION grprocess;
+#endif
 
 int greenroompipe[2];
 int greenroompipe_in[2]={-1,-1};
-// TODO: Handle outgoing cam, sending to greenroom if we don't have the broadcast password yet
-// TODO: Option to show greenroom as non-mod
+char greenroom_gotpass=0;
 
 struct greenmap
 {
@@ -248,6 +251,27 @@ static gboolean greenroom_handleline(GIOChannel* iochannel, GIOCondition conditi
     greenroom_updatecount();
     return 1;
   }
+  if(!strcmp(buf, "Starting outgoing media stream"))
+  {
+    struct camera* cam=camera_new(nickname, "out", CAMFLAG_NONE);
+    AVCodec* codec=avcodec_find_encoder(AV_CODEC_ID_FLV1);
+    cam->vctx=avcodec_alloc_context3(codec);
+    cam->vctx->pix_fmt=AV_PIX_FMT_YUV420P;
+    cam->vctx->time_base.num=1;
+    cam->vctx->time_base.den=10;
+    cam->vctx->width=camsize_out.width;
+    cam->vctx->height=camsize_out.height;
+    avcodec_open2(cam->vctx, codec, 0);
+    cam->frame->data[0]=0;
+    cam->frame->width=0;
+    cam->frame->height=0;
+    cam->dstframe->data[0]=0;
+
+    cam->actx=0;
+    updatescaling(0, 0, 1);
+    gtk_widget_show_all(cam->box);
+    return 1;
+  }
   if(!strncmp(buf, "Captcha: ", 9))
   {
     // If we're a mod, don't bother with the captcha (since we're only here to look at cams)
@@ -314,7 +338,7 @@ void greenroom_join(const char* id)
   strcat(cmd, channel);
   strcat(cmd, " ");
   strcat(cmd, id);
-  w32_runcmdpipes(cmd, greenroompipe_in, greenroompipe, coreprocess);
+  w32_runcmdpipes(cmd, greenroompipe_in, greenroompipe, grprocess);
 #else
   pipe(greenroompipe);
   pipe(greenroompipe_in);
@@ -361,3 +385,31 @@ void greenroom_changenick(const char* from, const char* to)
     }
   }
 }
+
+void greenroom_allowed(void)
+{
+  greenroom_gotpass=1;
+  if(camera_find("out"))
+  {
+    camout_delay=600;
+    write(greenroompipe_in[1], "/camdown\n", 9);
+    write(tc_client_in[1], "/camup\n", 7);
+  }
+}
+
+void greenroom_indicator(GdkPixbuf* frame)
+{
+  static GdkPixbuf* indicator=0;
+  static double indicatorwidth;
+  static double indicatorheight;
+  if(!indicator)
+  {
+    indicator=gdk_pixbuf_new_from_file(frombuild ? "greenroomindicator.png" : PREFIX "/share/tc_client/greenroomindicator.png", 0);
+    if(!indicator){return;}
+    indicatorwidth=gdk_pixbuf_get_width(indicator);
+    indicatorheight=gdk_pixbuf_get_height(indicator);
+  }
+  int framewidth=gdk_pixbuf_get_width(frame);
+  int frameheight=gdk_pixbuf_get_height(frame);
+  gdk_pixbuf_composite(indicator, frame, 0, frameheight*3/4, framewidth, frameheight/4, 0, frameheight*3/4, (double)framewidth/indicatorwidth, (double)frameheight/indicatorheight/4.0, GDK_INTERP_BILINEAR, 255);
+}
diff --git a/utilities/gtk/greenroom.h b/utilities/gtk/greenroom.h
index 98d12d0..676b22e 100644
--- a/utilities/gtk/greenroom.h
+++ b/utilities/gtk/greenroom.h
@@ -15,6 +15,9 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 extern int greenroompipe_in[2];
+extern char greenroom_gotpass;
 extern char greenroom_gotnick(const char* id, const char* nick);
 extern void greenroom_join(const char* id);
 extern void greenroom_changenick(const char* from, const char* to);
+extern void greenroom_allowed(void);
+extern void greenroom_indicator(GdkPixbuf* frame);
diff --git a/utilities/gtk/greenroomindicator.xcf b/utilities/gtk/greenroomindicator.xcf
new file mode 100644
index 0000000..28594fa
Binary files /dev/null and b/utilities/gtk/greenroomindicator.xcf differ
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index b3d89c2..37b69f2 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -33,6 +33,8 @@
 #include "../compat.h"
 #include "../compat_av.h"
 #include "gui.h"
+#include "main.h"
+#include "greenroom.h"
 #include "media.h"
 
 #define PREVIEW_MAX_WIDTH 640
@@ -57,6 +59,7 @@ CAM* camout_cam=0;
 char pushtotalk_enabled=0;
 char pushtotalk_pushed=0;
 #endif
+unsigned int camout_delay;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 void camera_playsnd(struct camera* cam, int16_t* samples, unsigned int samplecount)
@@ -258,12 +261,12 @@ void camera_cleanup(void)
 
 void freebuffer(guchar* pixels, gpointer data){free(pixels);}
 
-unsigned int camout_delay;
 void startcamout(CAM* cam)
 {
   if(!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_mic"))))
   { // Only /camup if we're not already broadcasting mic
-    dprintf(tc_client_in[1], "/camup\n");
+    int fd=((hasgreenroom && !greenroom_gotpass)?greenroompipe_in[1]:tc_client_in[1]);
+    dprintf(fd, "/camup\n");
   }
   camout_cam=cam;
   camout_delay=500;
@@ -310,6 +313,14 @@ gboolean cam_encode(void* camera_)
   // Scale to fit
   gdkframe=gdk_pixbuf_scale_simple(gdkframe, camsize_scale.width, camsize_scale.height, GDK_INTERP_BILINEAR);
   volume_indicator(gdkframe, cam); // Add volume indicator
+  int fd;
+  if(hasgreenroom && !greenroom_gotpass)
+  {
+    fd=greenroompipe_in[1];
+    greenroom_indicator(gdkframe); // Add greenroom indicator
+  }else{
+    fd=tc_client_in[1];
+  }
   gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
   g_object_unref(oldpixbuf);
   // Encode
@@ -333,9 +344,9 @@ gboolean cam_encode(void* camera_)
   char key=!!(packet.flags&AV_PKT_FLAG_KEY);
   unsigned char frameinfo=(key?0x12:0x22); // In the first 4 bits: 1=keyframe, 2=interframe
   // Send video
-  dprintf(tc_client_in[1], "/video %i\n", packet.size+1);
-  write(tc_client_in[1], &frameinfo, 1);
-  ssize_t w=write(tc_client_in[1], packet.data, packet.size);
+  dprintf(fd, "/video %i\n", packet.size+1);
+  write(fd, &frameinfo, 1);
+  ssize_t w=write(fd, packet.data, packet.size);
 if(w!=packet.size){printf("Error: wrote %zi of %i bytes\n", w, packet.size);}
 
   av_packet_unref(&packet);
@@ -661,9 +672,10 @@ gboolean mic_encode(GIOChannel* iochannel, GIOCondition condition, gpointer data
   if(avcodec_receive_packet(avctx, &packet)){return 1;}
   unsigned char frameinfo=0x6c; // 6=Nellymoser, 3<<2=44100 samplerate
   // Send audio
-  dprintf(tc_client_in[1], "/audio %i\n", packet.size+1);
-  write(tc_client_in[1], &frameinfo, 1);
-  write(tc_client_in[1], packet.data, packet.size);
+  int fd=((hasgreenroom && !greenroom_gotpass)?greenroompipe_in[1]:tc_client_in[1]);
+  dprintf(fd, "/audio %i\n", packet.size+1);
+  write(fd, &frameinfo, 1);
+  write(fd, packet.data, packet.size);
 
   av_packet_unref(&packet);
   return 1;
@@ -691,10 +703,12 @@ void volume_indicator(GdkPixbuf* frame, struct camera* cam)
   guchar* pixels=gdk_pixbuf_get_pixels(frame);
   unsigned int channels=gdk_pixbuf_get_n_channels(frame);
   unsigned int stride=gdk_pixbuf_get_rowstride(frame);
-  unsigned int size_x=camsize_scale.width/24;
-  unsigned int size_y=camsize_scale.height/5;
-  unsigned int pos_x=camsize_scale.width*47/48-size_x;
-  unsigned int pos_y=camsize_scale.height*47/48-size_y;
+  unsigned int width=gdk_pixbuf_get_width(frame);
+  unsigned int height=gdk_pixbuf_get_height(frame);
+  unsigned int size_x=width/24;
+  unsigned int size_y=height/5;
+  unsigned int pos_x=width*47/48-size_x;
+  unsigned int pos_y=height*47/48-size_y;
   int volumebar=size_y-cam->volume*size_y;
   if(volumebar<0){volumebar=0;}
   unsigned int x, y;
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index c205428..f9a16aa 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -70,6 +70,7 @@ extern GdkPixbufAnimationIter* camplaceholder_iter;
 extern CAM* camout_cam;
 extern char pushtotalk_enabled;
 extern char pushtotalk_pushed;
+extern unsigned int camout_delay;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 extern void camera_playsnd(struct camera* cam, int16_t* samples, unsigned int samplecount);