$ git clone http://tcclient.ion.nu/tc_client.git
commit e2b0a27f9138fdc09288835943f526cf6221dafb
Author: Alicia <...>
Date: Sat Dec 17 10:40:23 2016 +0100
tc_client-gtk: added support for viewing and approving greenroom cameras.
diff --git a/ChangeLog b/ChangeLog
index 9ea198b..bc4dad3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -25,6 +25,7 @@ tc_client-gtk: added an icon to mark moderators in the user list.
tc_client-gtk: disable the input field and the broadcast menu when in "lurker" mode.
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.
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 7116b3d..e667f06 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ IRCHACK_OBJ=utilities/irchack/irchack.o utilities/compat.o
MODBOT_OBJ=utilities/modbot/modbot.o utilities/list.o utilities/modbot/queue.o utilities/compat.o
CAMVIEWER_OBJ=utilities/camviewer/camviewer.o utilities/compat.o utilities/compat_av.o libcamera.a
CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o utilities/cursedchat/buffer.o utilities/compat.o utilities/list.o
-TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/configfile.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o utilities/gtk/postproc.o utilities/compat.o utilities/compat_av.o utilities/gtk/inputhistory.o utilities/gtk/playmedia.o libcamera.a
+TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/configfile.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o utilities/gtk/postproc.o utilities/compat.o utilities/compat_av.o utilities/gtk/inputhistory.o utilities/gtk/playmedia.o utilities/gtk/greenroom.o libcamera.a
LIBCAMERA_OBJ=utilities/libcamera/camera.o utilities/libcamera/camera_img.o
UTILS=irchack modbot
CONFINFO=|Will enable the IRC utility irchack|Will enable the bot utility modbot
@@ -153,7 +153,7 @@ SOURCES+=utilities/irchack/irchack.c
SOURCES+=utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/queue.h utilities/modbot/commands.html
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/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 gtkgui.glade
+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/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
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index d05b45a..d42cbef 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -60,6 +60,7 @@
#include "../stringutils.h"
#include "inputhistory.h"
#include "playmedia.h"
+#include "greenroom.h"
#include "main.h"
struct viddata
@@ -75,8 +76,8 @@ int tc_client_in[2];
const char* channel=0;
const char* mycolor=0;
char* nickname=0;
+char hasgreenroom=0;
char frombuild=0; // Running from the build directory
-#define TC_CLIENT (frombuild?"./tc_client":"tc_client")
#ifdef _WIN32
PROCESS_INFORMATION coreprocess={.hProcess=0};
#endif
@@ -143,9 +144,9 @@ void printchat(const char* text, const char* color, unsigned int offset, const c
}
unsigned int cameventsource=0;
-char buf[1024];
gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
{
+ static char buf[1024];
gsize r;
unsigned int i;
for(i=0; i<1023; ++i)
@@ -160,14 +161,6 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
char* sizestr=strchr(&buf[7], ' ');
if(!sizestr){return 1;}
sizestr[0]=0;
- // Find the camera representation for the given ID
- struct camera* cam=camera_find(&buf[7]);
- if(!cam){return 1;}
- if(!cam->vctx)
- {
- cam->vctx=avcodec_alloc_context3(data->vdecoder);
- avcodec_open2(cam->vctx, data->vdecoder, 0);
- }
unsigned int size=strtoul(&sizestr[1], 0, 0);
if(!size){return 1;}
// Mostly ignore the first byte (contains frame type (e.g. keyframe etc.) in 4 bits and codec in the other 4)
@@ -176,6 +169,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
av_init_packet(&pkt);
unsigned char databuf[size+4];
pkt.data=databuf;
+ pkt.size=size;
unsigned char frameinfo;
g_io_channel_read_chars(iochannel, (gchar*)&frameinfo, 1, 0, 0);
// printf("Frametype-frame: %x\n", ((unsigned int)frameinfo&0xf0)/16);
@@ -187,33 +181,10 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
pos+=r;
}
if((frameinfo&0xf)!=2){return 1;} // Not FLV1, get data but discard it
+ // Find the camera representation for the given ID
+ struct camera* cam=camera_find(&buf[7]);
if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
- pkt.size=size;
- int gotframe;
- avcodec_send_packet(cam->vctx, &pkt);
- gotframe=avcodec_receive_frame(cam->vctx, cam->frame);
- if(gotframe){return 1;}
-
- if(cam->placeholder) // Remove the placeholder animation if it has it
- {
- g_source_remove(cam->placeholder);
- cam->placeholder=0;
- }
- // Scale and convert to RGB24 format
- unsigned int bufsize=av_image_get_buffer_size(AV_PIX_FMT_RGB24, camsize_scale.width, camsize_scale.height, 1);
- unsigned char* buf=malloc(bufsize);
- cam->dstframe->data[0]=buf;
- cam->dstframe->linesize[0]=camsize_scale.width*3;
- struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, camsize_scale.width, camsize_scale.height, AV_PIX_FMT_RGB24, SWS_BICUBIC, 0, 0, 0);
- sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
- sws_freeContext(swsctx);
- postprocess(&cam->postproc, cam->dstframe->data[0], camsize_scale.width, camsize_scale.height);
-
- GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cam->cam));
- GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, camsize_scale.width, camsize_scale.height, cam->dstframe->linesize[0], freebuffer, 0);
- volume_indicator(gdkframe, cam);
- gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
- if(oldpixbuf){g_object_unref(oldpixbuf);}
+ camera_decode(cam, &pkt, camsize_scale.width, camsize_scale.height);
return 1;
}
if(!strncmp(buf, "Audio: ", 7))
@@ -242,23 +213,29 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
struct camera* cam=camera_find(&buf[7]);
if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
if(!cam->actx && camera_init_audio(cam, frameinfo)){return 1;}
- int gotframe;
- avcodec_send_packet(cam->actx, &pkt);
- gotframe=avcodec_receive_frame(cam->actx, cam->frame);
- if(gotframe){return 1;}
- camera_calcvolume(cam, (float*)cam->frame->data[0], cam->frame->nb_samples);
- unsigned int samplecount=cam->frame->nb_samples*SAMPLERATE_OUT/cam->samplerate;
- int16_t outbuf[samplecount];
- void* outdata[]={outbuf, 0};
- #ifdef HAVE_AVRESAMPLE
- int outlen=avresample_convert(cam->resamplectx, (void*)outdata, samplecount*sizeof(uint8_t), samplecount, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples);
- #else
- int outlen=swr_convert(cam->swrctx, (void*)outdata, samplecount, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
- #endif
- if(outlen>0){camera_playsnd(cam, outbuf, outlen);}
+ mic_decode(cam, &pkt);
#endif
return 1;
}
+ if(!strncmp(buf, "Nickname of connection ", 23))
+ {
+ char* nick=strstr(&buf[23], ": ");
+ if(nick)
+ {
+ nick[0]=0;
+ if(greenroom_gotnick(&buf[23], &nick[2]))
+ {
+ char buf[strlen(&nick[2])+strlen(" is waiting in the greenroom0")];
+ strcpy(buf, &nick[2]);
+ strcat(buf, " is waiting in the greenroom");
+ printchat(buf, 0, 0, 0);
+ }else{
+ nick[0]=':';
+ printchat(buf, 0, 0, 0);
+ }
+ }
+ return 1;
+ }
if(!strncmp(buf, "Currently online: ", 18))
{
printchat(buf, 0, 0, 0);
@@ -279,6 +256,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
unsigned int length=strlen(&buf[15]);
nickname=malloc(length+strlen("guest-")+1);
sprintf(nickname, "guest-%s", &(buf[15]));
+ if(hasgreenroom){greenroom_join(&buf[15]);}
return 1;
}
if(!strncmp(buf, "Captcha: ", 9))
@@ -479,6 +457,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
free(nickname);
nickname=strdup(&space[21]);
}
+ greenroom_changenick(nick, &space[21]);
}
}
free(color);
@@ -530,14 +509,14 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
if(!idend){return 1;}
idend[0]=0;
camera_remove(nick, 1); // Remove any duplicates
- struct camera* cam=camera_new(nick, id);
+ struct camera* cam=camera_new(nick, id, CAMFLAG_NONE);
updatescaling(0, 0, 1);
gtk_widget_show_all(cam->box);
return 1;
}
if(!strcmp(buf, "Starting outgoing media stream"))
{
- struct camera* cam=camera_new(nickname, "out");
+ struct camera* cam=camera_new(nickname, "out", CAMFLAG_NONE);
cam->vctx=avcodec_alloc_context3(data->vencoder);
cam->vctx->pix_fmt=AV_PIX_FMT_YUV420P;
cam->vctx->time_base.num=1;
@@ -577,6 +556,11 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
gui_disableinputs();
return 1;
}
+ if(!strcmp(buf, "Channel has greenroom"))
+ {
+ hasgreenroom=1;
+ return 1;
+ }
if(!strcmp(buf, "Server disconnected"))
{
printchat(buf, 0, 0, 0);
@@ -831,7 +815,8 @@ void sendmessage(GtkEntry* entry, void* x)
!strcmp(msg, "/camup") ||
!strcmp(msg, "/camdown") ||
!strncmp(msg, "/video ", 7) ||
- !strncmp(msg, "/topic ", 7))
+ !strncmp(msg, "/topic ", 7) ||
+ !strncmp(msg, "/getnick ", 9))
{
gtk_entry_set_text(entry, "");
sendingmsg=0;
@@ -981,6 +966,7 @@ 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
diff --git a/utilities/gtk/greenroom.c b/utilities/gtk/greenroom.c
new file mode 100644
index 0000000..77c6e68
--- /dev/null
+++ b/utilities/gtk/greenroom.c
@@ -0,0 +1,362 @@
+/*
+ tc_client-gtk, a graphical user interface for tc_client
+ Copyright (C) 2016 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
+ the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ 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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#ifndef NO_PRCTL
+ #include <sys/prctl.h>
+#endif
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <libswscale/swscale.h>
+#if LIBAVUTIL_VERSION_MAJOR>50 || (LIBAVUTIL_VERSION_MAJOR==50 && LIBAVUTIL_VERSION_MINOR>37)
+ #include <libavutil/imgutils.h>
+#else
+ #include <libavcore/imgutils.h>
+#endif
+#include "compat.h"
+#include "gui.h"
+#include "main.h"
+#include "configfile.h"
+#include "userlist.h"
+#include "greenroom.h"
+
+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
+
+struct greenmap
+{
+ const char* id;
+ const char* nick;
+ const char* camid;
+};
+static struct greenmap* users=0;
+static unsigned int usercountgr=0;
+static GtkWidget* menu=0;
+static GtkWidget* menuitem=0;
+
+static void greenroom_updatecount(void)
+{
+ // Ignore any leftover users that we didn't find the nickname for
+ unsigned int count=0;
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i){count+=!!users[i].nick;}
+ if(count)
+ {
+ char buf[snprintf(0, 0, "Greenroom (%u)", count)+1];
+ sprintf(buf, "Greenroom (%u)", count);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), buf);
+ }else{
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), "Greenroom");
+ }
+}
+
+static void greenroom_remove(const char* id, char cam)
+{
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i)
+ {
+ const char* str=(cam?users[i].camid:users[i].id);
+ if(str && !strcmp(str, id))
+ {
+ free((void*)users[i].id);
+ free((void*)users[i].nick);
+ free((void*)users[i].camid);
+ --usercountgr;
+ memmove(&users[i], &users[i+1], sizeof(struct greenmap)*(usercountgr-i));
+ greenroom_updatecount();
+ return;
+ }
+ }
+}
+
+static void greenroom_allow(GtkWidget* menuitem, void* x)
+{
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i)
+ {
+ if(!strcmp(users[i].id, x))
+ {
+ dprintf(tc_client_in[1], "/allow %s\n", users[i].nick);
+ }
+ }
+}
+
+static gboolean greenroom_handleline(GIOChannel* iochannel, GIOCondition condition, gpointer x)
+{
+ static char buf[1024];
+ gsize r;
+ unsigned int i;
+ for(i=0; i<1023; ++i)
+ {
+ g_io_channel_read_chars(iochannel, &buf[i], 1, &r, 0);
+ if(r<1){printf("No more data\n"); gtk_main_quit(); return 0;}
+ if(buf[i]=='\r'||buf[i]=='\n'){break;}
+ }
+ buf[i]=0;
+ char* space=strchr(buf, ' ');
+ if(!strncmp(buf, "Video: ", 7))
+ {
+ char* sizestr=strchr(&buf[7], ' ');
+ if(!sizestr){return 1;}
+ sizestr[0]=0;
+ unsigned int size=strtoul(&sizestr[1], 0, 0);
+ if(!size){return 1;}
+ // Mostly ignore the first byte (contains frame type (e.g. keyframe etc.) in 4 bits and codec in the other 4)
+ --size;
+ AVPacket pkt;
+ av_init_packet(&pkt);
+ unsigned char databuf[size+4];
+ pkt.data=databuf;
+ pkt.size=size;
+ unsigned char frameinfo;
+ g_io_channel_read_chars(iochannel, (gchar*)&frameinfo, 1, 0, 0);
+ unsigned int pos=0;
+ while(pos<size)
+ {
+ g_io_channel_read_chars(iochannel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
+ }
+ if((frameinfo&0xf)!=2){return 1;} // Not FLV1, get data but discard it
+ // Find the camera representation for the given ID
+ struct camera* cam=camera_find(&buf[7]);
+ if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
+ camera_decode(cam, &pkt, 160, 120);
+ return 1;
+ }
+ if(!strncmp(buf, "Audio: ", 7))
+ {
+ char* sizestr=strchr(&buf[7], ' ');
+ if(!sizestr){return 1;}
+ sizestr[0]=0;
+ unsigned int size=strtoul(&sizestr[1], 0, 0);
+ if(!size){return 1;}
+ unsigned char frameinfo;
+ g_io_channel_read_chars(iochannel, (gchar*)&frameinfo, 1, 0, 0);
+ --size; // For the byte we read above
+ AVPacket pkt;
+ av_init_packet(&pkt);
+ unsigned char databuf[size];
+ pkt.data=databuf;
+ pkt.size=size;
+ unsigned int pos=0;
+ while(pos<size)
+ {
+ g_io_channel_read_chars(iochannel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
+ }
+#ifdef HAVE_LIBAO
+ if(gtk_widget_is_visible(menu)) // Only play audio when looking at the greenroom
+ {
+ // Find the camera representation for the given ID (for decoder context)
+ struct camera* cam=camera_find(&buf[7]);
+ if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
+ if(!cam->actx && camera_init_audio(cam, frameinfo)){return 1;}
+ mic_decode(cam, &pkt);
+ }
+#endif
+ return 1;
+ }
+ if(!strncmp(buf, "VideoEnd: ", 10))
+ {
+ // TODO: Deal with potential collisions with cams in the main channel
+ camera_remove(&buf[10], 0);
+ greenroom_remove(&buf[10], 1);
+ return 1;
+ }
+ // For both "currently on cam" and "cammed up", add to a list and reply with a "/getnick", whose response will complete the item and then we can announce it
+ if(space && menu && !strcmp(space, " cammed up"))
+ {
+ // Find out who this cam belongs to
+ space[0]=0;
+ dprintf(tc_client_in[1], "/getnick %s\n", buf);
+ ++usercountgr;
+ users=realloc(users, sizeof(struct greenmap)*usercountgr);
+ users[usercountgr-1].id=strdup(buf);
+ users[usercountgr-1].nick=0;
+ users[usercountgr-1].camid=0;
+ return 1;
+ }
+ if(menu && !strncmp(buf, "Currently on cam: ", 18))
+ {
+ char* next=&buf[16];
+ while(next)
+ {
+ char* user=&next[2];
+ next=strstr(user, ", ");
+ if(!user[0]){continue;}
+ if(next){next[0]=0;}
+ dprintf(tc_client_in[1], "/getnick %s\n", user);
+ ++usercountgr;
+ users=realloc(users, sizeof(struct greenmap)*usercountgr);
+ users[usercountgr-1].id=strdup(user);
+ users[usercountgr-1].nick=0;
+ users[usercountgr-1].camid=0;
+ }
+ return 1;
+ }
+ if(!strncmp(buf, "Starting media stream for ", 26))
+ {
+ char* nick=&buf[26];
+ char* id=strstr(nick, " (");
+ if(!id){return 1;}
+ id[0]=0;
+ id=&id[2];
+ char* idend=strchr(id, ')');
+ if(!idend){return 1;}
+ idend[0]=0;
+ camera_remove(nick, 1); // Remove any duplicates
+ struct camera* cam=camera_new(nick, id, CAMFLAG_GREENROOM);
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i)
+ {
+ if(!strcmp(users[i].id, nick))
+ {
+ cam->label=gtk_label_new(users[i].nick);
+ users[i].camid=strdup(id);
+ }
+ }
+ // Add it to the greenroom menu
+ cam->box=gtk_menu_item_new();
+ GtkWidget* box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start(GTK_BOX(box), cam->cam, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(box), cam->label, 0, 0, 0);
+ gtk_container_add(GTK_CONTAINER(cam->box), box);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), cam->box);
+ g_signal_connect(cam->box, "activate", G_CALLBACK(greenroom_allow), cam->nick);
+ gtk_widget_show_all(cam->box);
+ greenroom_updatecount();
+ 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)
+ if(user_ismod(nickname))
+ {
+ write(greenroompipe_in[1], "\n", 1);
+ }else{
+ gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "captcha")));
+ 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);
+ }
+ return 1;
+ }
+ if(buf[0]=='['&&isdigit(buf[1])&&isdigit(buf[2])&&buf[3]==':'&&isdigit(buf[4])&&isdigit(buf[5])&&buf[6]==']'&&buf[7]==' ')
+ {
+ space=strchr(&buf[8], ' ');
+ if(!strcmp(space, " left the channel"))
+ {
+ space[0]=0;
+ camera_remove(&buf[8], 1);
+ greenroom_remove(&buf[8], 0);
+ }
+ return 1;
+ }
+ return 1;
+}
+
+char greenroom_gotnick(const char* id, const char* nick)
+{
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i)
+ {
+ if(!strcmp(users[i].id, id))
+ {
+ free((void*)users[i].nick); // just in case
+ users[i].nick=strdup(nick);
+ dprintf(greenroompipe_in[1], "/opencam %s\n", id);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void greenroom_join(const char* id)
+{
+ // Only add greenroom menu if mod (or optionally see greenroom anyway?)
+ if(user_ismod(nickname))
+ {
+ menuitem=gtk_menu_item_new_with_label("Greenroom");
+ gtk_menu_shell_append(GTK_MENU_SHELL(gtk_builder_get_object(gui, "menubar")), menuitem);
+ menu=gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_widget_show_all(menuitem);
+ gtk_widget_show_all(menu);
+ }
+#ifdef _WIN32
+ char cmd[strlen("./tc_client --greenroom --cookies tinychat_no_account.cookie 0")+strlen(channel)+strlen(id)];
+ strcpy(cmd, "./tc_client --greenroom ");
+ if(config_get_bool("storecookies"))
+ {
+ strcat(cmd, "--cookies tinychat_no_account.cookie");
+ }
+ strcat(cmd, channel);
+ strcat(cmd, " ");
+ strcat(cmd, id);
+ w32_runcmdpipes(cmd, greenroompipe_in, greenroompipe, coreprocess);
+#else
+ pipe(greenroompipe);
+ pipe(greenroompipe_in);
+ if(!fork())
+ {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ close(greenroompipe[0]);
+ close(greenroompipe_in[1]);
+ dup2(greenroompipe[1], 1);
+ dup2(greenroompipe_in[0], 0);
+ if(config_get_bool("storecookies"))
+ {
+ const char* home=getenv("HOME");
+
+ char filename[strlen(home)+strlen("/.config/tc_client-gtk.cookies/no_account0")];
+ sprintf(filename, "%s/.config", home);
+ mkdir(filename, 0700);
+ sprintf(filename, "%s/.config/tc_client-gtk.cookies", home);
+ mkdir(filename, 0700);
+ sprintf(filename, "%s/.config/tc_client-gtk.cookies/no_account", home);
+ execlp(TC_CLIENT, TC_CLIENT, "--greenroom", "--cookies", filename, channel, id, (char*)0);
+ }else{
+ execlp(TC_CLIENT, TC_CLIENT, "--greenroom", channel, id, (char*)0);
+ }
+ _exit(1);
+ }
+#endif
+ GIOChannel* tcchannel=g_io_channel_unix_new(greenroompipe[0]);
+ g_io_channel_set_encoding(tcchannel, 0, 0);
+ g_io_add_watch(tcchannel, G_IO_IN, greenroom_handleline, 0);
+}
+
+void greenroom_changenick(const char* from, const char* to)
+{
+ unsigned int i;
+ for(i=0; i<usercountgr; ++i)
+ {
+ if(users[i].nick && !strcmp(users[i].nick, from))
+ {
+ free((void*)users[i].nick);
+ users[i].nick=strdup(to);
+ struct camera* cam=camera_find(users[i].camid);
+ if(cam){gtk_label_set_text(GTK_LABEL(cam->label), to);}
+ }
+ }
+}
diff --git a/utilities/gtk/greenroom.h b/utilities/gtk/greenroom.h
new file mode 100644
index 0000000..98d12d0
--- /dev/null
+++ b/utilities/gtk/greenroom.h
@@ -0,0 +1,20 @@
+/*
+ tc_client-gtk, a graphical user interface for tc_client
+ Copyright (C) 2016 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
+ the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ 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/>.
+*/
+extern int greenroompipe_in[2];
+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);
diff --git a/utilities/gtk/main.h b/utilities/gtk/main.h
index 55a46f0..3624296 100644
--- a/utilities/gtk/main.h
+++ b/utilities/gtk/main.h
@@ -14,7 +14,12 @@
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/>.
*/
+#define TC_CLIENT (frombuild?"./tc_client":"tc_client")
+extern int tc_client_in[2];
+extern const char* channel;
extern char* nickname;
+extern char hasgreenroom;
+extern char frombuild;
extern gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x);
extern void togglecam(GtkCheckMenuItem* item, void* x);
#ifdef HAVE_PULSEAUDIO
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 353c5e8..b3d89c2 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -160,7 +160,7 @@ struct camera* camera_findbynick(const char* nick)
return 0;
}
-struct camera* camera_new(const char* nick, const char* id)
+struct camera* camera_new(const char* nick, const char* id, unsigned char flags)
{
++camcount;
cams=realloc(cams, sizeof(struct camera)*camcount);
@@ -181,19 +181,26 @@ struct camera* camera_new(const char* nick, const char* id)
cam->frame=av_frame_alloc();
cam->dstframe=av_frame_alloc();
cam->cam=gtk_image_new();
- cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_box_set_homogeneous(GTK_BOX(cam->box), 0);
- // Wrap cam image in an event box to catch (right) clicks
- GtkWidget* eventbox=gtk_event_box_new();
- gtk_container_add(GTK_CONTAINER(eventbox), cam->cam);
- gtk_event_box_set_above_child(GTK_EVENT_BOX(eventbox), 1);
- cam->label=gtk_label_new(cam->nick);
- gtk_box_pack_start(GTK_BOX(cam->box), eventbox, 0, 0, 0);
- gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
- g_signal_connect(eventbox, "button-release-event", G_CALLBACK(gui_show_cam_menu), cam->id);
- cam->placeholder=g_timeout_add(100, camplaceholder_update, cam->id);
+ if(flags&CAMFLAG_GREENROOM)
+ {
+ cam->box=0;
+ cam->placeholder=0;
+ }else{
+ cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_set_homogeneous(GTK_BOX(cam->box), 0);
+ // Wrap cam image in an event box to catch (right) clicks
+ GtkWidget* eventbox=gtk_event_box_new();
+ gtk_container_add(GTK_CONTAINER(eventbox), cam->cam);
+ gtk_event_box_set_above_child(GTK_EVENT_BOX(eventbox), 1);
+ cam->label=gtk_label_new(cam->nick);
+ gtk_box_pack_start(GTK_BOX(cam->box), eventbox, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
+ g_signal_connect(eventbox, "button-release-event", G_CALLBACK(gui_show_cam_menu), cam->id);
+ cam->placeholder=g_timeout_add(100, camplaceholder_update, cam->id);
+ }
cam->volume=0;
cam->volumeold=1024;
+ cam->flags=flags;
// Initialize postprocessing values
postproc_init(&cam->postproc);
return cam;
@@ -451,7 +458,13 @@ const char* camselect_file(void)
void updatescaling(unsigned int width, unsigned int height, char changedcams)
{
- if(!camcount){return;}
+ unsigned int boxcount=0;
+ unsigned int i;
+ for(i=0; i<camcount; ++i)
+ {
+ if(!cams[i].flags&CAMFLAG_GREENROOM){++boxcount;}
+ }
+ if(!boxcount){return;}
if(!width){width=gtk_widget_get_allocated_width(GTK_WIDGET(gtk_builder_get_object(gui, "main")));}
if(!height){height=gtk_widget_get_allocated_height(GTK_WIDGET(gtk_builder_get_object(gui, "camerascroll")));}
@@ -461,11 +474,11 @@ void updatescaling(unsigned int width, unsigned int height, char changedcams)
camsize_scale.height=1;
unsigned int rowcount=1;
unsigned int rows;
- for(rows=1; rows<=camcount; ++rows)
+ for(rows=1; rows<=boxcount; ++rows)
{
struct size scale;
- unsigned int cams_per_row=camcount/rows;
- if(camcount%rows){++cams_per_row;}
+ unsigned int cams_per_row=boxcount/rows;
+ if(boxcount%rows){++cams_per_row;}
scale.width=width/cams_per_row;
// 3/4 ratio
scale.height=scale.width*3/4;
@@ -484,11 +497,11 @@ void updatescaling(unsigned int width, unsigned int height, char changedcams)
}else if(scale.width<camsize_scale.width){break;} // Only getting smaller from here, use the last one that increased
}
- unsigned int i;
if(rowcount!=camrowcount || changedcams) // Changed the number of rows, shuffle everything around to fit. Or added/removed a camera, in which case we need to shuffle things around anyway
{
for(i=0; i<camcount; ++i)
{
+ if(cams[i].flags&CAMFLAG_GREENROOM){continue;}
g_object_ref(cams[i].box); // Increase reference counts so that they are not deallocated while they are temporarily detached from the rows
GtkContainer* parent=GTK_CONTAINER(gtk_widget_get_parent(cams[i].box));
if(parent){gtk_container_remove(parent, cams[i].box);}
@@ -503,11 +516,14 @@ void updatescaling(unsigned int width, unsigned int height, char changedcams)
gtk_widget_set_halign(camrows[i], GTK_ALIGN_CENTER);
gtk_widget_show(camrows[i]);
}
- unsigned int cams_per_row=camcount/camrowcount;
- if(camcount%camrowcount){++cams_per_row;}
+ unsigned int cams_per_row=boxcount/camrowcount;
+ if(boxcount%camrowcount){++cams_per_row;}
+ unsigned int index=0;
for(i=0; i<camcount; ++i)
{
- gtk_box_pack_start(GTK_BOX(camrows[i/cams_per_row]), cams[i].box, 0, 0, 0);
+ if(cams[i].flags&CAMFLAG_GREENROOM){continue;}
+ gtk_box_pack_start(GTK_BOX(camrows[index/cams_per_row]), cams[i].box, 0, 0, 0);
+ ++index;
g_object_unref(cams[i].box); // Decrease reference counts once they're attached again
}
}
@@ -517,6 +533,7 @@ void updatescaling(unsigned int width, unsigned int height, char changedcams)
// Rescale current images to fit
for(i=0; i<camcount; ++i)
{
+ if(cams[i].flags&CAMFLAG_GREENROOM){continue;}
GdkPixbuf* pixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cams[i].cam));
if(!pixbuf){continue;}
GdkPixbuf* old=pixbuf;
@@ -699,3 +716,58 @@ void volume_indicator(GdkPixbuf* frame, struct camera* cam)
}
++cam->volumeold;
}
+
+void camera_decode(struct camera* cam, AVPacket* pkt, unsigned int width, unsigned int height)
+{
+ if(!cam->vctx)
+ {
+ AVCodec* codec=avcodec_find_decoder(AV_CODEC_ID_FLV1);
+ cam->vctx=avcodec_alloc_context3(codec);
+ avcodec_open2(cam->vctx, codec, 0);
+ }
+ int gotframe;
+ avcodec_send_packet(cam->vctx, pkt);
+ gotframe=avcodec_receive_frame(cam->vctx, cam->frame);
+ if(gotframe){return;}
+
+ if(cam->placeholder) // Remove the placeholder animation if it has it
+ {
+ g_source_remove(cam->placeholder);
+ cam->placeholder=0;
+ }
+ // Scale and convert to RGB24 format
+ unsigned int bufsize=av_image_get_buffer_size(AV_PIX_FMT_RGB24, width, height, 1);
+ unsigned char* buf=malloc(bufsize);
+ cam->dstframe->data[0]=buf;
+ cam->dstframe->linesize[0]=width*3;
+ struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, width, height, AV_PIX_FMT_RGB24, SWS_BICUBIC, 0, 0, 0);
+ sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
+ sws_freeContext(swsctx);
+ postprocess(&cam->postproc, cam->dstframe->data[0], width, height);
+
+ GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cam->cam));
+ GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, width, height, cam->dstframe->linesize[0], freebuffer, 0);
+ volume_indicator(gdkframe, cam);
+ gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
+ if(oldpixbuf){g_object_unref(oldpixbuf);}
+}
+
+#ifdef HAVE_LIBAO
+void mic_decode(struct camera* cam, AVPacket* pkt)
+{
+ int gotframe;
+ avcodec_send_packet(cam->actx, pkt);
+ gotframe=avcodec_receive_frame(cam->actx, cam->frame);
+ if(gotframe){return;}
+ camera_calcvolume(cam, (float*)cam->frame->data[0], cam->frame->nb_samples);
+ unsigned int samplecount=cam->frame->nb_samples*SAMPLERATE_OUT/cam->samplerate;
+ int16_t outbuf[samplecount];
+ void* outdata[]={outbuf, 0};
+#ifdef HAVE_AVRESAMPLE
+ int outlen=avresample_convert(cam->resamplectx, (void*)outdata, samplecount*sizeof(uint8_t), samplecount, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples);
+#else
+ int outlen=swr_convert(cam->swrctx, (void*)outdata, samplecount, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
+#endif
+ if(outlen>0){camera_playsnd(cam, outbuf, outlen);}
+}
+#endif
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index e58d101..c205428 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -27,6 +27,8 @@
#include "../libcamera/camera.h"
#include "postproc.h"
#define SAMPLERATE_OUT 11025 // 11025 is the most common input sample rate, and it is more CPU-efficient to keep it that way than upsampling or downsampling it when converting from flt to s16
+#define CAMFLAG_NONE 0
+#define CAMFLAG_GREENROOM 1
struct camera
{
AVFrame* frame;
@@ -50,6 +52,7 @@ struct camera
unsigned int samplerate;
float volume;
unsigned int volumeold;
+ unsigned char flags;
};
struct size
{
@@ -75,7 +78,7 @@ extern gboolean audiomixer(void* p);
extern void camera_remove(const char* id, char isnick);
extern struct camera* camera_find(const char* id);
extern struct camera* camera_findbynick(const char* nick);
-extern struct camera* camera_new(const char* nick, const char* id);
+extern struct camera* camera_new(const char* nick, const char* id, unsigned char flags);
extern char camera_init_audio(struct camera* cam, uint8_t frameinfo);
extern void camera_cleanup(void);
extern void freebuffer(guchar* pixels, gpointer data);
@@ -94,4 +97,8 @@ extern void* audiothread_in(void* fdp);
extern gboolean mic_encode(GIOChannel* iochannel, GIOCondition condition, gpointer datap);
extern void camera_calcvolume(struct camera* cam, float* samples, unsigned int samplecount);
extern void volume_indicator(GdkPixbuf* frame, struct camera* cam);
+extern void camera_decode(struct camera* cam, AVPacket* pkt, unsigned int width, unsigned int height);
+#ifdef HAVE_LIBAO
+extern void mic_decode(struct camera* cam, AVPacket* pkt);
+#endif
#endif
diff --git a/utilities/gtk/userlist.c b/utilities/gtk/userlist.c
index 3873c74..d04593a 100644
--- a/utilities/gtk/userlist.c
+++ b/utilities/gtk/userlist.c
@@ -156,3 +156,10 @@ void userlist_sort(void)
gtk_box_reorder_child(GTK_BOX(userlistwidget), userlist[i].item, position);
}
}
+
+char user_ismod(const char* nick)
+{
+ struct user* user=finduser(nick);
+ if(!user){return 0;}
+ return user->ismod;
+}
diff --git a/utilities/gtk/userlist.h b/utilities/gtk/userlist.h
index ebcf7b8..0315ca8 100644
--- a/utilities/gtk/userlist.h
+++ b/utilities/gtk/userlist.h
@@ -37,3 +37,4 @@ extern void renameuser(const char* old, const char* newnick);
extern void removeuser(const char* nick);
extern void usersetmod(const char* nick, char mod);
extern void userlist_sort(void);
+extern char user_ismod(const char* nick);