$ git clone http://tcclient.ion.nu/tc_client.git
commit 657bc37a45715952b36551deadb2de08bd0498c9
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.27
diff --git a/ChangeLog b/ChangeLog
index 9dfa01b..d591221 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+0.27:
+Added some more compatibility hacks for android.
+Added support for video broadcasting (adding /camup and /video <length> followed by the encoded data for each frame)
+crossbuild.sh: build ncurses and readline for the target platform.
+cursedchat: disable readline's match-listing, which does not work well with the curses interface.
+cursedchat: made tab completion work with nicknames.
0.26:
modbot: fixed a segfault when trying to skip more videos than what's in the queue.
modbot: fixed a segfault when receiving a manual /mbc command but no video is playing.
diff --git a/Makefile b/Makefile
index da7c8da..4480abe 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
-VERSION=0.26
+VERSION=0.27
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
ifneq ($(wildcard config.mk),)
include config.mk
endif
OBJ=client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o media.o
-IRCHACK_OBJ=utilities/irchack/irchack.o
-MODBOT_OBJ=utilities/modbot/modbot.o utilities/modbot/list.o utilities/modbot/queue.o
+IRCHACK_OBJ=utilities/irchack/irchack.o utilities/compat.o
+MODBOT_OBJ=utilities/modbot/modbot.o utilities/list.o utilities/modbot/queue.o
CAMVIEWER_OBJ=utilities/camviewer/camviewer.o
-CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o utilities/cursedchat/buffer.o
+CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o utilities/cursedchat/buffer.o utilities/compat.o utilities/list.o
UTILS=irchack modbot
ifdef GTK_LIBS
ifdef AVCODEC_LIBS
@@ -21,6 +21,9 @@ ifdef SWSCALE_LIBS
CFLAGS+=-DHAVE_SOUND $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
endif
endif
+ ifdef LIBV4L2_LIBS
+ CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
+ endif
endif
endif
endif
@@ -28,7 +31,7 @@ endif
ifdef CURSES_LIBS
ifdef READLINE_LIBS
UTILS+=cursedchat
- CFLAGS+=$(CURSES_CFLAGS)
+ CFLAGS+=$(CURSES_CFLAGS) $(READLINE_CFLAGS)
endif
endif
@@ -44,7 +47,7 @@ modbot: $(MODBOT_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
camviewer: $(CAMVIEWER_OBJ)
- $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) -o $@
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) -o $@
cursedchat: $(CURSEDCHAT_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
@@ -53,4 +56,4 @@ clean:
rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) $(CURSEDCHAT_OBJ) tc_client irchack modbot camviewer cursedchat
tarball:
- tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endian.c media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h media.h LICENSE README ChangeLog crossbuild.sh utilities/irchack/irchack.c utilities/modbot/modbot.c utilities/modbot/list.c utilities/modbot/list.h utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h configure
+ tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endian.c media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h media.h LICENSE README ChangeLog crossbuild.sh utilities/irchack/irchack.c utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h utilities/compat.c utilities/compat.h utilities/list.c utilities/list.h configure
diff --git a/README b/README
index bc658a0..438e4b5 100644
--- a/README
+++ b/README
@@ -14,19 +14,17 @@ Commands supported by tc_client:
/ban <nick> = ban someone
/banlist = list who is banned
/forgive <nick/ID> = unban someone
+/camup = open an audio/video stream for broadcasting your video
+/video <length> = send a <length> bytes long encoded frame, send the frame data after this line
/help = list these commands at runtime
-Some things that may never change:
-*tc_client can't stream/broadcast your webcam/mic
-*tc_client itself won't play youtube videos, but applications that rely on tc_client can interpret the /mbs, /mbsk and /mbc commands
-
Current commands sent by the TC servers that tc_client doesn't know how to handle:
notice (some, notice is used for many functions)
joinsdone
-avons (list of people currently on cam)
pros
Included applications that rely on tc_client (type 'make utils' to build):
-*irchack = a minimal IRC server that translates between IRC and tc_client's commands
-*modbot = a bot that handles youtube video requests with a queue and an approval system to keep inappropriate videos from being played. Supports the following commands: !help, !request, !queue (show queue status), for mods only: !playnext (try playing next in queue without marking it yet), !approve, !badvid (Note: modbot depends on youtube-dl to find out the length of videos)
-*camviewer = an example application for viewing cam streams
+*irchack = a minimal IRC server that translates between IRC and tc_client's commands
+*modbot = a bot that handles youtube video requests with a queue and an approval system to keep inappropriate videos from being played. Supports the following commands: !help, !request, !queue (show queue status), for mods only: !playnext (try playing next in queue without marking it yet), !approve, !badvid (Note: modbot depends on youtube-dl to find out the length of videos)
+*camviewer = an example application for viewing cam streams
+*cursedchat = a curses chat interface
diff --git a/client.c b/client.c
index 3a3b5fa..48af852 100644
--- a/client.c
+++ b/client.c
@@ -120,7 +120,7 @@ char* gethost(char *channel, char *password)
return host;
}
-char* getkey(char* id, char* channel)
+char* getkey(const char* id, const char* channel)
{
char url[strlen("http://tinychat.com/api/captcha/check.php?guest%5Fid=&room=tinychat%5E0")+strlen(id)+strlen(channel)];
sprintf(url, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%s&room=tinychat%%5E%s", id, channel);
@@ -142,6 +142,23 @@ char* getkey(char* id, char* channel)
return key;
}
+char* getbroadcastkey(const char* channel, const char* nick)
+{
+ unsigned int id=idlist_get(nick);
+ char url[strlen("http://tinychat.com/api/broadcast.pw?name=&site=tinychat&nick=&id=0")+128+strlen(channel)+strlen(nick)];
+ sprintf(url, "http://tinychat.com/api/broadcast.pw?name=%s&site=tinychat&nick=%s&id=%u", channel, nick, id);
+ char* response=http_get(url, 0);
+ char* key=strstr(response, " token='");
+
+ if(!key){return 0;}
+ key+=8;
+ char* keyend=strchr(key, '\'');
+ if(!keyend){return 0;}
+ key=strndup(key, keyend-key);
+ free(response);
+ return key;
+}
+
char* getmodkey(const char* user, const char* pass, const char* channel, char* loggedin)
{
// TODO: if possible, do this in a neater way than digging the key out from an HTML page.
@@ -415,7 +432,9 @@ int main(int argc, char** argv)
"/forgive <nick/ID> = unban someone\n"
"/names = list everyone that is online\n"
"/mute = temporarily mutes all non-moderator broadcasts\n"
- "/push2talk = puts all non-operators in push2talk mode\n");
+ "/push2talk = puts all non-operators in push2talk mode\n"
+ "/camup = open an audio/video stream for broadcasting your video\n"
+ "/video <length> = send a <length> bytes long encoded frame, send the frame data after this line\n");
fflush(stdout);
}
else if(!strncmp(buf, "/color", 6) && (!buf[6]||buf[6]==' '))
@@ -556,6 +575,29 @@ int main(int argc, char** argv)
amfsend(&amf, sock);
continue;
}
+ else if(!strcmp(buf, "/camup"))
+ {
+ // Retrieve and send the key for broadcasting access
+ char* key=getbroadcastkey(channel, nickname);
+ amfinit(&amf, 3);
+ amfstring(&amf, "bauth");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, key);
+ amfsend(&amf, sock);
+ free(key);
+ // Initiate stream
+ streamout_start(idlist_get(nickname), sock);
+ continue;
+ }
+ else if(!strncmp(buf, "/video ", 7)) // Send video data
+ {
+ size_t len=strtoul(&buf[7],0,10);
+ char buf[len];
+ fullread(0, buf, len);
+ stream_sendvideo(sock, buf, len);
+ continue;
+ }
}
char* msg=tonumlist(buf, len);
amfinit(&amf, 3);
diff --git a/configure b/configure
index 28bb16d..5bff5c5 100755
--- a/configure
+++ b/configure
@@ -132,4 +132,14 @@ else
fi
rm -f readlinetest.c readlinetest
+printf 'Checking for libv4l2... '
+libs="`pkg-config --libs libv4l2 2> /dev/null`"
+if [ "x$libs" != "x" ]; then
+ echo "LIBV4L2_LIBS=${libs}" >> config.mk
+ echo "LIBV4L2_CFLAGS=`pkg-config --cflags libv4l2`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
echo Done
diff --git a/crossbuild.sh b/crossbuild.sh
index 481b401..84893a4 100755
--- a/crossbuild.sh
+++ b/crossbuild.sh
@@ -5,8 +5,35 @@ if [ "$host" = "" ]; then
if [ "$host" = "" ]; then host='i386-pc-linux-gnu'; fi
echo "No target host specified (argv[1]), defaulting to ${host}"
fi
-./configure --host="$host"
here="`pwd`"
+export PATH="${here}/curlprefix/bin:${here}/ncursesprefix/bin:${PATH}"
+if [ ! -e ncursesprefix ]; then
+ wget -c http://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz
+ tar -xzf ncurses-5.9.tar.gz
+ cd ncurses-5.9
+ mkdir build
+ cd build
+ # Some hackery to build ncursesw5-config, which seems to get disabled along with --disable-database
+ ../configure --prefix="${here}/ncursesprefix" --host="${host}" --enable-static --disable-shared --with-termlib --enable-widec --without-cxx ac_cv_header_locale_h=no
+ mv Makefile Makefile.tmp
+ ../configure --prefix="${here}/ncursesprefix" --host="${host}" --enable-static --disable-shared --with-termlib --enable-widec --without-cxx --disable-database --with-fallbacks=linux ac_cv_header_locale_h=no
+ mv Makefile.tmp Makefile
+ make
+ make install
+ cd "$here"
+ cp ncursesprefix/lib/libtinfow.a ncursesprefix/lib/libtinfo.a
+fi
+if [ ! -e readlineprefix ]; then
+ wget -c http://ftp.gnu.org/gnu/readline/readline-6.3.tar.gz
+ tar -xzf readline-6.3.tar.gz
+ cd readline-6.3
+ mkdir build
+ cd build
+ ../configure --prefix="${here}/readlineprefix" --host="${host}" --enable-static --disable-shared bash_cv_wcwidth_broken=no
+ make
+ make install
+ cd "$here"
+fi
if [ ! -e curlprefix ]; then
wget -c http://curl.haxx.se/download/curl-7.40.0.tar.bz2
tar -xjf curl-7.40.0.tar.bz2
@@ -18,6 +45,7 @@ if [ ! -e curlprefix ]; then
make install
cd "$here"
fi
+./configure --host="$host" > config.log 2>&1
if grep -q 'LIBS+=-liconv' config.mk && [ ! -e iconvprefix ]; then
wget -c http://ftp.gnu.org/gnu/libiconv/libiconv-1.14.tar.gz
tar -xzf libiconv-1.14.tar.gz
@@ -29,8 +57,9 @@ if grep -q 'LIBS+=-liconv' config.mk && [ ! -e iconvprefix ]; then
make install
cd "$here"
fi
-export PATH="${here}/curlprefix/bin:${PATH}"
echo "CFLAGS+=-I${here}/iconvprefix/include" >> config.mk
echo "LDFLAGS+=-L${here}/iconvprefix/lib" >> config.mk
+echo "READLINE_CFLAGS=-I${here}/readlineprefix/include" >> config.mk
+echo "READLINE_LIBS=-L${here}/readlineprefix/lib -lreadline" >> config.mk
make
make utils
diff --git a/media.c b/media.c
index a4e7bf3..649c2f7 100644
--- a/media.c
+++ b/media.c
@@ -44,6 +44,7 @@ void stream_start(const char* nick, int sock) // called upon privmsg "/opencam .
streams=realloc(streams, sizeof(struct stream)*streamcount);
streams[streamcount-1].userid=userid;
streams[streamcount-1].streamid=streamid;
+ streams[streamcount-1].outgoing=0;
struct rtmp amf;
amfinit(&amf, 3);
amfstring(&amf, "createStream");
@@ -53,6 +54,24 @@ void stream_start(const char* nick, int sock) // called upon privmsg "/opencam .
printf("Starting media stream for %s (%u)\n", nick, userid);
}
+void streamout_start(unsigned int id, int sock) // called upon privmsg "/camup"
+{
+ unsigned int streamid=1;
+ while(stream_idtaken(streamid)){++streamid;}
+ ++streamcount;
+ streams=realloc(streams, sizeof(struct stream)*streamcount);
+ streams[streamcount-1].userid=id;
+ streams[streamcount-1].streamid=streamid;
+ streams[streamcount-1].outgoing=1;
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "createStream");
+ amfnum(&amf, streamid+1);
+ amfnull(&amf);
+ amfsend(&amf, sock);
+ printf("Starting outgoing media stream\n");
+}
+
void stream_play(struct amf* amf, int sock) // called upon _result
{
unsigned int i;
@@ -62,12 +81,13 @@ void stream_play(struct amf* amf, int sock) // called upon _result
{
struct rtmp amf;
amfinit(&amf, 8);
- amfstring(&amf, "play");
+ amfstring(&amf, streams[i].outgoing?"publish":"play");
amfnum(&amf, 0);
amfnull(&amf);
char camid[snprintf(0,0,"%u0", streams[i].userid)];
sprintf(camid, "%u", streams[i].userid);
amfstring(&amf, camid);
+ if(streams[i].outgoing){amfstring(&amf, "live");}
amf.msgid=le32(streams[i].streamid);
amfsend(&amf, sock);
return;
@@ -118,3 +138,22 @@ void stream_handlestatus(struct amf* amf)
}
}
}
+
+void stream_sendvideo(int sock, void* buf, size_t len)
+{
+ unsigned int i;
+ for(i=0; i<streamcount; ++i)
+ {
+ if(streams[i].outgoing)
+ {
+ struct rtmp msg;
+ msg.type=RTMP_VIDEO;
+ msg.chunkid=6;
+ msg.length=len;
+ msg.msgid=streams[i].streamid;
+ msg.buf=buf;
+ rtmp_send(sock, &msg);
+ return;
+ }
+ }
+}
diff --git a/media.h b/media.h
index 728c3dd..71893cf 100644
--- a/media.h
+++ b/media.h
@@ -20,12 +20,15 @@ struct stream
{
unsigned int streamid;
unsigned int userid;
+ char outgoing;
};
extern struct stream* streams;
extern unsigned int streamcount;
extern void stream_start(const char* nick, int sock); // called upon privmsg "/opencam ..."
+extern void streamout_start(unsigned int id, int sock); // called upon privmsg "/camup"
extern void stream_play(struct amf* amf, int sock); // called upon _result
extern void stream_handledata(struct rtmp* rtmp);
extern void stream_handlestatus(struct amf* amf);
+extern void stream_sendvideo(int sock, void* buf, size_t len);
diff --git a/numlist.c b/numlist.c
index b6b15e5..e39aad6 100644
--- a/numlist.c
+++ b/numlist.c
@@ -31,7 +31,12 @@ char* fromnumlist(char* in, size_t* outlen)
++len;
x=&x[1];
}
+#ifdef __ANDROID__
+ *outlen=len;
+ unsigned char* string=malloc(len+1);
+#else
unsigned short string[len+1];
+#endif
int i;
for(i=0; i<len; ++i)
{
@@ -41,6 +46,9 @@ char* fromnumlist(char* in, size_t* outlen)
}
string[len]=0;
+#ifdef __ANDROID__
+ return string;
+#else
iconv_t cd=iconv_open("", "utf-16");
char* outbuf=malloc(len*4);
char* i_out=outbuf;
@@ -53,10 +61,14 @@ char* fromnumlist(char* in, size_t* outlen)
iconv_close(cd);
*outlen-=remaining;
return outbuf;
+#endif
}
char* tonumlist(char* i_in, size_t len)
{
+#ifdef __ANDROID__
+ #define in i_in
+#else
iconv_t cd=iconv_open("utf-16le", "");
unsigned short in[len+1];
char* i_out=(char*)in;
@@ -64,6 +76,7 @@ char* tonumlist(char* i_in, size_t len)
while(outlen>0 && len>0 && iconv(cd, &i_in, &len, &i_out, &outlen)>0);
iconv_close(cd);
len=((void*)i_out-(void*)in)/2;
+#endif
char* out=malloc(len*strlen("65535,"));
out[0]=0;
diff --git a/rtmp.c b/rtmp.c
index 035ff7a..2956d12 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -84,8 +84,9 @@ char rtmp_get(int sock, struct rtmp* rtmp)
if(fmt<3)
{
// Timestamp
- fullread(sock, &x, 3);
- chunk->timestamp=x;
+ x=0;
+ fullread(sock, ((void*)&x)+1, 3);
+ chunk->timestamp=be32(x);
if(fmt<2)
{
// Length
@@ -166,16 +167,17 @@ void rtmp_send(int sock, struct rtmp* rtmp)
{
write(sock, &rtmp->msgid, sizeof(rtmp->msgid));
}
- // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
+ // Send 128 bytes at a time separated by a "continuation header", the 0xc3 byte for chunk 3
void* pos=rtmp->buf;
unsigned int len=rtmp->length;
+ basicheader|=0xc0; // Turn it into a "continuation header" by setting format to 3
while(len>0)
{
int w;
if(len>128)
{
w=write(sock, pos, 128);
- w+=write(sock, "\xc3", 1);
+ w+=write(sock, &basicheader, 1);
len-=128;
}else{
w=write(sock, pos, len);
diff --git a/rtmp.h b/rtmp.h
index 6c03274..aa91445 100644
--- a/rtmp.h
+++ b/rtmp.h
@@ -33,5 +33,6 @@ struct rtmp
void* buf;
};
+extern size_t fullread(int fd, void* buf, size_t len);
extern char rtmp_get(int sock, struct rtmp* rtmp);
extern void rtmp_send(int sock, struct rtmp* rtmp);
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index 43a8094..df8ad53 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -16,13 +16,23 @@
*/
#include <unistd.h>
#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
+#include <libavutil/imgutils.h>
#ifdef HAVE_SOUND
-#include <libswresample/swresample.h>
-#include <ao/ao.h>
+// TODO: use libavresample instead if available
+ #include <libswresample/swresample.h>
+ #include <ao/ao.h>
#endif
#include <gtk/gtk.h>
+#undef HAVE_V4L2 // Not working yet, something is wrong with the frames making the encoding break (for keyframes in particular and I don't know why)
+#ifdef HAVE_V4L2
+ #include <libv4l2.h>
+ #include <linux/videodev2.h>
+#endif
#if GTK_MAJOR_VERSION==2
#define GTK_ORIENTATION_HORIZONTAL 0
@@ -58,6 +68,7 @@ struct viddata
unsigned int camcount;
GtkWidget* box;
AVCodec* vdecoder;
+// AVCodec* vencoder;
AVCodec* adecoder;
#ifdef HAVE_SOUND
int audiopipe;
@@ -164,6 +175,27 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
gtk_widget_show_all(cam->box);
return 1;
}
+/*
+ if(!strcmp(buf, "Starting outgoing media stream"))
+ {
+ ++data->camcount;
+ data->cams=realloc(data->cams, sizeof(struct camera)*data->camcount);
+ struct camera* cam=&data->cams[data->camcount-1];
+ cam->frame=av_frame_alloc();
+ cam->dstframe=av_frame_alloc();
+ cam->nick=strdup("You");
+ cam->id=strdup("out");
+ cam->vctx=avcodec_alloc_context3(data->vdecoder);
+ avcodec_open2(cam->vctx, data->vdecoder, 0);
+ cam->cam=gtk_image_new();
+ cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(cam->box), gtk_label_new(cam->nick), 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
+ gtk_widget_show_all(cam->box);
+ return 1;
+ }
+*/
if(!strncmp(buf, "VideoEnd: ", 10))
{
for(i=0; i<data->camcount; ++i)
@@ -212,7 +244,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
if(!strcmp(data->cams[i].id, &buf[7])){cam=&data->cams[i]; break;}
}
- if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); free(pkt.data); return 1;}
+ if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
int gotframe;
avcodec_decode_audio4(cam->actx, cam->frame, &gotframe, &pkt);
if(!gotframe){return 1;}
@@ -249,7 +281,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
pos+=read(tc_client[0], pkt.data+pos, size-pos);
}
if((frameinfo&0xf)!=2){return 1;} // Not FLV1, get data but discard it
- if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); free(pkt.data); return 1;}
+ if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
pkt.size=size;
int gotframe;
avcodec_decode_video2(cam->vctx, cam->frame, &gotframe, &pkt);
@@ -295,6 +327,85 @@ void audiothread(int fd)
}
#endif
+#ifdef HAVE_V4L2
+void camup(GtkWidget* button, struct viddata* data)
+{
+ dprintf(tc_client_in[1], "/camup\n");
+ gtk_widget_destroy(button); // Only once
+// printf("Camming up!\n");
+ if(!fork())
+ {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ unsigned int delay=500000;
+ // Set up camera
+ int fd=v4l2_open("/dev/video0", O_RDWR);
+ struct v4l2_format fmt;
+ fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt.fmt.pix.width=320;
+ fmt.fmt.pix.height=240;
+ fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB24;
+ fmt.fmt.pix.field=V4L2_FIELD_NONE;
+ fmt.fmt.pix.bytesperline=fmt.fmt.pix.width*3;
+ fmt.fmt.pix.sizeimage=fmt.fmt.pix.bytesperline*fmt.fmt.pix.height;
+ v4l2_ioctl(fd, VIDIOC_S_FMT, &fmt);
+ AVCodecContext* ctx=avcodec_alloc_context3(data->vencoder);
+ ctx->width=fmt.fmt.pix.width;
+ ctx->height=fmt.fmt.pix.height;
+ ctx->pix_fmt=PIX_FMT_YUV420P;
+ ctx->time_base.num=1;
+ ctx->time_base.den=10;
+ avcodec_open2(ctx, data->vencoder, 0);
+ AVFrame* frame=av_frame_alloc();
+ frame->format=PIX_FMT_RGB24;
+ frame->width=fmt.fmt.pix.width;
+ frame->height=fmt.fmt.pix.height;
+ av_image_alloc(frame->data, frame->linesize, ctx->width, ctx->height, frame->format, 1);
+ AVPacket packet;
+ packet.buf=0;
+ packet.data=0;
+ packet.size=0;
+ packet.dts=AV_NOPTS_VALUE;
+ packet.pts=AV_NOPTS_VALUE;
+
+ // Set up frame for conversion from the camera's format to a format the encoder can use
+ AVFrame* dstframe=av_frame_alloc();
+ dstframe->format=ctx->pix_fmt;
+ dstframe->width=ctx->width;
+ dstframe->height=ctx->height;
+ av_image_alloc(dstframe->data, dstframe->linesize, ctx->width, ctx->height, ctx->pix_fmt, 1);
+
+ struct SwsContext* swsctx=sws_getContext(frame->width, frame->height, PIX_FMT_RGB24, frame->width, frame->height, AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
+
+ while(1)
+ {
+ usleep(delay);
+ if(delay>100000){delay-=50000;}
+ v4l2_read(fd, frame->data[0], fmt.fmt.pix.sizeimage);
+ int gotpacket;
+ sws_scale(swsctx, (const uint8_t*const*)frame->data, frame->linesize, 0, frame->height, dstframe->data, dstframe->linesize);
+ av_init_packet(&packet);
+ packet.data=0;
+packet.size=0;
+ avcodec_encode_video2(ctx, &packet, dstframe, &gotpacket);
+ unsigned char frameinfo=0x20;
+// if(packet.flags&AV_PKT_FLAG_KEY){printf("Sending keyframe!\n");}
+ if(packet.flags&AV_PKT_FLAG_KEY){frameinfo|=0x01;}else{frameinfo|=0x02;}
+ dprintf(tc_client_in[1], "/video %i\n", packet.size+1);
+ write(tc_client_in[1], &frameinfo, 1);
+ write(tc_client_in[1], packet.data, packet.size);
+ // Also send the packet to our main thread so we can see ourselves
+ dprintf(tc_client[1], "Video: out %i\n", packet.size+1);
+ write(tc_client[1], &frameinfo, 1);
+ write(tc_client[1], packet.data, packet.size);
+
+ av_free_packet(&packet);
+ }
+ sws_freeContext(swsctx);
+ _exit(0);
+ }
+}
+#endif
+
int main(int argc, char** argv)
{
struct viddata data={0,0,0,0,0};
@@ -310,6 +421,7 @@ int main(int argc, char** argv)
data.audiopipe=audiopipe[1];
if(!fork())
{
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
close(audiopipe[1]);
audiothread(audiopipe[0]);
_exit(0);
@@ -322,12 +434,19 @@ int main(int argc, char** argv)
g_signal_connect(w, "destroy", gtk_main_quit, 0);
data.box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add(GTK_CONTAINER(w), data.box);
+#ifdef HAVE_V4L2
+ data.vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
+ GtkWidget* cambutton=gtk_button_new_with_label("Broadcast cam");
+ g_signal_connect(cambutton, "clicked", G_CALLBACK(camup), &data);
+ gtk_box_pack_start(GTK_BOX(data.box), cambutton, 0, 0, 0);
+#endif
gtk_widget_show_all(w);
pipe(tc_client);
pipe(tc_client_in);
if(!fork())
{
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
close(tc_client[0]);
close(tc_client_in[1]);
dup2(tc_client[1], 1);
@@ -335,7 +454,6 @@ int main(int argc, char** argv)
argv[0]="./tc_client";
execv("./tc_client", argv);
}
- close(tc_client[1]);
close(tc_client_in[0]);
GIOChannel* tcchannel=g_io_channel_unix_new(tc_client[0]);
g_io_channel_set_encoding(tcchannel, 0, 0);
diff --git a/utilities/compat.c b/utilities/compat.c
new file mode 100644
index 0000000..e620559
--- /dev/null
+++ b/utilities/compat.c
@@ -0,0 +1,35 @@
+/*
+ Some compatibility code to work on more limited platforms
+ Copyright (C) 2015 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 "compat.h"
+#ifdef __ANDROID__
+// Android has no dprintf, so we make our own
+#include <stdarg.h>
+size_t dprintf(int fd, const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int len=vsnprintf(0, 0, fmt, va);
+ va_end(va);
+ char buf[len+1];
+ va_start(va, fmt);
+ vsnprintf(buf, len+1, fmt, va);
+ va_end(va);
+ buf[len]=0;
+ write(fd, buf, len);
+ return len;
+}
+#endif
diff --git a/utilities/compat.h b/utilities/compat.h
new file mode 100644
index 0000000..2cfb39b
--- /dev/null
+++ b/utilities/compat.h
@@ -0,0 +1,21 @@
+/*
+ Some compatibility code to work on more limited platforms
+ Copyright (C) 2015 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/>.
+*/
+#ifdef __ANDROID__
+#include <stdint.h>
+extern size_t dprintf(int fd, const char* fmt, ...);
+#define mbtowc(x,y,z) 1
+#endif
diff --git a/utilities/cursedchat/cursedchat.c b/utilities/cursedchat/cursedchat.c
index 42545f5..8a30f75 100644
--- a/utilities/cursedchat/cursedchat.c
+++ b/utilities/cursedchat/cursedchat.c
@@ -19,9 +19,12 @@
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
+#include <termios.h>
#include <locale.h>
#include <curses.h>
#include <readline/readline.h>
+#include "../compat.h"
+#include "../list.h"
#include "buffer.h"
#define HALFSCREEN (LINES>4?(LINES-3)/2:1)
@@ -30,6 +33,7 @@ WINDOW* topic;
char* channeltopic;
WINDOW* input;
int to_app;
+struct list userlist={0,0};
// Translate ANSI escape codes to curses commands and write the text to a window
void waddansi(WINDOW* w, char* str)
@@ -284,6 +288,31 @@ void resizechat(int sig)
drawinput();
}
+void dontprintmatches(char** matches, int num, int maxlen)
+{
+}
+
+unsigned int completionmatch;
+char* completenicks(const char* text, int state)
+{
+ // text is the word we're completing on, state is the iteration count (one iteration per matching name, until we return 0)
+ if(!state){completionmatch=0;}
+ while(completionmatch<userlist.itemcount)
+ {
+ if(!strncmp(userlist.items[completionmatch], text, strlen(text)))
+ {
+ char* completion=malloc(strlen(userlist.items[completionmatch])+2);
+ strcpy(completion, userlist.items[completionmatch]);
+ // Check if we're on the first word and only add the ":" if we are
+ if(strlen(text)>=rl_point){strcat(completion, ":");}
+ ++completionmatch;
+ return completion;
+ }
+ ++completionmatch;
+ }
+ return 0;
+}
+
int main(int argc, char** argv)
{
if(argc<3){execv("./tc_client", argv); return 1;}
@@ -313,6 +342,8 @@ int main(int argc, char** argv)
rl_initialize();
rl_callback_handler_install(0, gotline);
rl_bind_key('\x1b', escinput);
+ rl_completion_display_matches_hook=dontprintmatches;
+ rl_completion_entry_function=completenicks;
wprintw(input, "> ");
wrefresh(topic);
wrefresh(input);
@@ -358,6 +389,20 @@ int main(int argc, char** argv)
channeltopic=strdup(&buf[12]);
drawtopic();
}
+ else if(!strncmp(buf, "Currently online: ", 18))
+ {
+ // Populate the userlist
+ char* name=&buf[16];
+ while(name)
+ {
+ name=&name[2];
+ char* next=strstr(name, ", ");
+ if(next){next[0]=0;}
+ list_add(&userlist, name);
+ if(next){next[0]=',';}
+ name=next;
+ }
+ }
else if(buf[0]=='['&&isdigit(buf[1])&&isdigit(buf[2])&&buf[3]==':'&&isdigit(buf[4])&&isdigit(buf[5])&&buf[6]==']'&&buf[7]==' ')
{
char* nick=&buf[8];
@@ -387,6 +432,8 @@ int main(int argc, char** argv)
else if(!strncmp(msg, " changed nickname to ", 21))
{
msg[0]=0;
+ // Update name in userlist
+ list_switch(&userlist, nick, &msg[21]);
unsigned int i;
// Prevent duplicate names for buffers, and all the issues that would bring
if((i=findbuffer(&msg[21])))
@@ -403,6 +450,20 @@ int main(int argc, char** argv)
}
msg[0]=' ';
}
+ else if(!strcmp(msg, " entered the channel"))
+ {
+ msg[0]=0;
+ // Add to the userlist
+ list_add(&userlist, nick);
+ msg[0]=' ';
+ }
+ else if(!strcmp(msg, " left the channel"))
+ {
+ msg[0]=0;
+ // Remove from the userlist
+ list_del(&userlist, nick);
+ msg[0]=' ';
+ }
}
waddstr(buffers[buffer].pad, "\n");
waddansi(buffers[buffer].pad, buf);
diff --git a/utilities/irchack/irchack.c b/utilities/irchack/irchack.c
index 47289c3..d9fc248 100644
--- a/utilities/irchack/irchack.c
+++ b/utilities/irchack/irchack.c
@@ -24,25 +24,7 @@
#include <sys/socket.h>
#include <ctype.h>
#include <signal.h>
-
-#ifdef __ANDROID__
-// Android has no dprintf, so we make our own
-#include <stdarg.h>
-size_t dprintf(int fd, const char* fmt, ...)
-{
- va_list va;
- va_start(va, fmt);
- int len=vsnprintf(0, 0, fmt, va);
- va_end(va);
- char buf[len+1];
- va_start(va, fmt);
- vsnprintf(buf, len+1, fmt, va);
- va_end(va);
- buf[len]=0;
- write(fd, buf, len);
- return len;
-}
-#endif
+#include "../compat.h"
// ANSI colors and their IRC equivalents
struct color{const char* ansi; const char* irc;};
diff --git a/utilities/modbot/list.c b/utilities/list.c
similarity index 100%
rename from utilities/modbot/list.c
rename to utilities/list.c
diff --git a/utilities/modbot/list.h b/utilities/list.h
similarity index 100%
rename from utilities/modbot/list.h
rename to utilities/list.h
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index e9d1111..3e84a77 100644
--- a/utilities/modbot/modbot.c
+++ b/utilities/modbot/modbot.c
@@ -26,7 +26,7 @@
#include <stdarg.h>
#include <time.h>
#include <termios.h>
-#include "list.h"
+#include "../list.h"
#include "queue.h"
struct list mods={0,0};
@@ -405,7 +405,7 @@ int main(int argc, char** argv)
}
say(pm, "%u video%s in queue, %u of which are not yet approved by mods (%s%s)\n", queue.itemcount, (queue.itemcount==1)?"":"s", notapproved, buf, (listed<notapproved)?", etc.":"");
}else{
- say(pm, "%u videos in queue\n", queue.itemcount);
+ say(pm, "%u video%s in queue\n", queue.itemcount, (queue.itemcount==1)?"":"s");
}
}
else if(!strcmp(msg, "!requestedby"))