$ git clone https://tcclient.ion.nu/tc_client.git
commit cc86304e817d91aaa0b59deb1d1883f725994b96
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.21
diff --git a/ChangeLog b/ChangeLog
index 4662717..a403f50 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+0.21:
+Renamed streamids to chunkids to better match the specification.
+Added a /close command for mods to close cam/mic streams.
+Added /ban, /banlist and /forgive commands.
+Added a /opencam command to receive cam data, and the 'camviewer' utility to handle the cam data (depends on libavcodec/libswscale and gtk+ 3.x), mostly as a reference.
+Do not echo while entering account password at runtime.
+Announce when someone cams up, and list who is on cam when joining.
+modbot: added !wrongrequest to undo a request.
+modbot: allow entering account password at runtime and without echo.
+modbot: added !skip/!skip <number> to skip videos in the queue.
+irchack: translate between /userinfo and WHOIS.
0.20:
Fixed amf0 reading of numbers (type 0)
Added support for sending format 0 RTMP packets (which include the msgid field)
diff --git a/Makefile b/Makefile
index b364d79..7cdc521 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,41 @@
-VERSION=0.20
+VERSION=0.21
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
+CAMVIEWER_OBJ=utilities/camviewer/camviewer.o
+UTILS=irchack modbot
+ifdef GTK_LIBS
+ifdef AVCODEC_LIBS
+ifdef AVUTIL_LIBS
+ifdef SWSCALE_LIBS
+ UTILS+=camviewer
+ CFLAGS+=$(GTK_CFLAGS) $(AVCODEC_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
+endif
+endif
+endif
+endif
+
+tc_client: $(OBJ)
+ $(CC) $(LDFLAGS) $^ $(LIBS) -o $@
+
+utils: $(UTILS)
+
+irchack: $(IRCHACK_OBJ)
+ $(CC) $(LDFLAGS) $^ $(LIBS) -o $@
-tc_client: client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o
+modbot: $(MODBOT_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
-utils: irchack modbot
+camviewer: $(CAMVIEWER_OBJ)
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) -o $@
clean:
- rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o tc_client irchack modbot
+ rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) tc_client irchack modbot camviewer
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 amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h LICENSE README ChangeLog crossbuild.sh irchack.c modbot.c 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/list.c utilities/modbot/list.h utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c configure
diff --git a/README b/README
index 73890b7..bc658a0 100644
--- a/README
+++ b/README
@@ -1,26 +1,32 @@
tc_client is a very primitive application, it doesn't implement a user interface on its own,
you could use it stand-alone but it will be ugly and if someone says something while you are typing a message your input-line will get cut off.
-Better interfaces can be implemented as wrappers that launch tc_client with stdin and stdout as pipes.
+Better interfaces can be implemented as wrappers that launch tc_client with stdin and stdout as pipes. See the bottom of this document for some included applications that run on top of tc_client.
Commands supported by tc_client:
-/msg <nickname> <message> = send a PM, incoming PMs look similar
-/color <on/off> = enable/disable showing colors with ANSI escapes
-/color <0-15> = set the color of your outgoing messages
-/colors = list the available colors
-/nick <newnick> = change nickname
-/help = list these commands at runtime
+/msg <to> <msg> = send a PM, incoming PMs look similar
+/color <0-15> = set the color of your outgoing messages
+/color <on/off> = enable/disable showing colors with ANSI escapes
+/color = see your current color
+/colors = list the available colors
+/nick <newnick> = change nickname
+/opencam <nick> = see someone's cam/mic (Warning: writes binary data to stdout)
+/close <nick> = close someone's cam/mic stream (as a mod)
+/ban <nick> = ban someone
+/banlist = list who is banned
+/forgive <nick/ID> = unban someone
+/help = list these commands at runtime
-Some things that will probably never change:
-*tc_client can't view people's webcams or listen to mics
+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
+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)
+*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
diff --git a/amfwriter.c b/amfwriter.c
index 76d06d2..3fc07dc 100644
--- a/amfwriter.c
+++ b/amfwriter.c
@@ -22,10 +22,10 @@
#include "rtmp.h"
#include "amfwriter.h"
-void amfinit(struct rtmp* msg, unsigned int streamid)
+void amfinit(struct rtmp* msg, unsigned int chunkid)
{
msg->type=RTMP_AMF0;
- msg->streamid=streamid;
+ msg->chunkid=chunkid;
msg->length=0;
msg->msgid=0;
msg->buf=0;
diff --git a/client.c b/client.c
index f2e8bcc..bb2c397 100644
--- a/client.c
+++ b/client.c
@@ -27,13 +27,13 @@
#include <sys/socket.h>
#include <locale.h>
#include <ctype.h>
+#include <termios.h>
#include <curl/curl.h>
-#include "rtmp.h"
-#include "amfparser.h"
#include "numlist.h"
-#include "amfwriter.h"
#include "idlist.h"
#include "colors.h"
+#include "media.h"
+#include "amfwriter.h"
struct writebuf
{
@@ -189,7 +189,7 @@ char* getprivfield(char* nick)
unsigned int id;
unsigned int privlen;
for(privlen=0; nick[privlen]&&nick[privlen]!=' '; ++privlen);
- id=idlist_get((char*)nick);
+ id=idlist_get(nick);
if(id<0)
{
nick[privlen]=0;
@@ -251,10 +251,17 @@ int main(int argc, char** argv)
setlocale(LC_ALL, "");
if(account_user && !account_pass) // Only username given, prompt for password
{
- fprintf(stderr, "Account password: ");
- fflush(stderr);
+ struct termios term;
+ tcgetattr(0, &term);
+ term.c_lflag&=~ECHO;
+ tcsetattr(0, TCSANOW, &term);
+ fprintf(stdout, "Account password: ");
+ fflush(stdout);
account_pass=malloc(128);
fgets(account_pass, 128, stdin);
+ term.c_lflag|=ECHO;
+ tcsetattr(0, TCSANOW, &term);
+ printf("\n");
unsigned int i;
for(i=0; account_pass[i]; ++i)
{
@@ -349,6 +356,7 @@ int main(int argc, char** argv)
amfsend(&amf, sock);
free(modkey);
+ char* unban=0;
struct pollfd pfd[2];
pfd[0].fd=0;
pfd[0].events=POLLIN;
@@ -363,7 +371,7 @@ int main(int argc, char** argv)
if(pfd[0].revents) // Got input, send a privmsg command
{
pfd[0].revents=0;
- unsigned char buf[2048];
+ char buf[2048];
unsigned int len=0;
int r;
while(len<2047)
@@ -379,7 +387,7 @@ int main(int argc, char** argv)
char* privfield=0;
if(buf[0]=='/') // Got a command
{
- if(!strcmp((char*)buf, "/help"))
+ if(!strcmp(buf, "/help"))
{
printf("/help = print this help text\n"
"/color <0-15> = pick color of your messages\n"
@@ -387,16 +395,21 @@ int main(int argc, char** argv)
"/color = see your current color\n"
"/colors = list the available colors and their numbers\n"
"/nick <newnick> = changes your nickname\n"
- "/msg <to> <msg> = send a private message\n");
+ "/msg <to> <msg> = send a private message\n"
+ "/opencam <nick> = see someone's cam/mic (Warning: writes binary data to stdout)\n"
+ "/close <nick> = close someone's cam/mic stream (as a mod)\n"
+ "/ban <nick> = ban someone\n"
+ "/banlist = list who is banned\n"
+ "/forgive <nick/ID> = unban someone\n");
fflush(stdout);
}
- else if(!strncmp((char*)buf, "/color", 6) && (!buf[6]||buf[6]==' '))
+ else if(!strncmp(buf, "/color", 6) && (!buf[6]||buf[6]==' '))
{
if(buf[6]) // Color specified
{
- if(!strcmp((char*)&buf[7], "off")){showcolor=0; continue;}
- if(!strcmp((char*)&buf[7], "on")){showcolor=1; continue;}
- currentcolor=atoi((char*)&buf[7]);
+ if(!strcmp(&buf[7], "off")){showcolor=0; continue;}
+ if(!strcmp(&buf[7], "on")){showcolor=1; continue;}
+ currentcolor=atoi(&buf[7]);
printf("\x1b[%smChanged color\x1b[0m\n", termcolors[currentcolor%16]);
}else{ // No color specified, state our current color
printf("\x1b[%smCurrent color: %i\x1b[0m\n", termcolors[currentcolor%16], currentcolor%16);
@@ -404,7 +417,7 @@ int main(int argc, char** argv)
fflush(stdout);
continue;
}
- else if(!strcmp((char*)buf, "/colors"))
+ else if(!strcmp(buf, "/colors"))
{
int i;
for(i=0; i<16; ++i)
@@ -414,9 +427,9 @@ int main(int argc, char** argv)
fflush(stdout);
continue;
}
- else if(!strncmp((char*)buf, "/nick ", 6))
+ else if(!strncmp(buf, "/nick ", 6))
{
- if((badchar=checknick((char*)&buf[6])))
+ if((badchar=checknick(&buf[6])))
{
printf("'%c' is not allowed in nicknames.\n", badchar);
continue;
@@ -425,40 +438,80 @@ int main(int argc, char** argv)
amfstring(&amf, "nick");
amfnum(&amf, 0);
amfnull(&amf);
- amfstring(&amf, (char*)&buf[6]);
+ amfstring(&amf, &buf[6]);
amfsend(&amf, sock);
continue;
}
- else if(!strncmp((char*)buf, "/msg ", 5))
+ else if(!strncmp(buf, "/msg ", 5))
{
- privfield=getprivfield((char*)&buf[5]);
+ privfield=getprivfield(&buf[5]);
if(!privfield){continue;}
}
- else if(!strncmp((char*)buf, "/priv ", 6))
+ else if(!strncmp(buf, "/priv ", 6))
{
- char* end=strchr((char*)&buf[6], ' ');
+ char* end=strchr(&buf[6], ' ');
if(!end){continue;}
- privfield=getprivfield((char*)&buf[6]);
+ privfield=getprivfield(&buf[6]);
if(!privfield){continue;}
len=strlen(&end[1]);
memmove(buf, &end[1], len+1);
}
-/* While we can get the server to send us video data, we don't know how to handle the data yet.
- else if(!strncmp((char*)buf, "/cam ", 5))
+ else if(!strncmp(buf, "/opencam ", 9))
+ {
+ stream_start(&buf[9], sock);
+ continue;
+ }
+ else if(!strncmp(buf, "/close ", 7)) // Stop someone's cam/mic broadcast
+ {
+ char nick[strlen(&buf[7])+1];
+ strcpy(nick, &buf[7]);
+ amfinit(&amf, 2);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ sprintf(buf, "_close%s", nick);
+ amfstring(&amf, buf);
+ amfsend(&amf, sock);
+ len=sprintf(buf, "closed: %s", nick);
+ }
+ else if(!strncmp(buf, "/ban ", 5)) // Ban someone
+ {
+ char nick[strlen(&buf[5])+1];
+ strcpy(nick, &buf[5]);
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ sprintf(buf, "notice%s%%20was%%20banned%%20by%%20%s%%20(%s)", nick, nickname, account_user);
+ amfstring(&amf, buf);
+ amfsend(&amf, sock);
+ // kick (this does the actual banning)
+ amfinit(&amf, 3);
+ amfstring(&amf, "kick");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, nick);
+ sprintf(buf, "%i", idlist_get(nick));
+ amfstring(&amf, buf);
+ amfsend(&amf, sock);
+ continue;
+ }
+ else if(!strcmp(buf, "/banlist") || !strncmp(buf, "/forgive ", 9))
{
- unsigned int id=idlist_get((char*)&buf[5]);
- camid=malloc(128);
- sprintf(camid, "%u", id);
+ if(buf[1]=='f') // forgive
+ {
+ free(unban);
+ unban=strdup(&buf[9]);
+ }
amfinit(&amf, 3);
- amfstring(&amf, "createStream");
- amfnum(&amf, 2);
+ amfstring(&amf, "banlist");
+ amfnum(&amf, 0);
amfnull(&amf);
amfsend(&amf, sock);
continue;
}
-*/
}
- char* msg=tonumlist((char*)buf, len);
+ char* msg=tonumlist(buf, len);
amfinit(&amf, 3);
amfstring(&amf, "privmsg");
amfnum(&amf, 0);
@@ -478,205 +531,252 @@ int main(int argc, char** argv)
// Got data from server
pfd[1].revents=0;
// Read the RTMP stream and handle AMF0 packets
- if(rtmp_get(sock, &rtmp))
+ char rtmpres=rtmp_get(sock, &rtmp);
+ if(!rtmpres){printf("Server disconnected\n"); break;}
+ if(rtmpres==2){continue;} // Not disconnected, but we didn't get a complete chunk yet either
+ if(rtmp.type==RTMP_VIDEO){stream_handledata(&rtmp); continue;}
+ if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); continue;}
+ struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
+ if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING)
{
-/* Getting video/cam data, but we don't know how to make use of it yet
- if(rtmp.type==RTMP_VIDEO)
- {
- write(vidf, rtmp.buf, rtmp.length);
- continue;
- }
-*/
- if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); continue;}
- struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
- if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING)
- {
-// printf("Got command: '%s'\n", amfin->items[0].string.string);
- if(!strcmp(amfin->items[0].string.string, "_error"))
- printamf(amfin);
- }
- if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "registered") && amfin->items[amfin->itemcount-1].type==AMF_STRING)
- {
- char* id=amfin->items[amfin->itemcount-1].string.string;
- printf("Guest ID: %s\n", id);
- char* key=getkey(id, channel);
+// printf("Got command: '%s'\n", amfin->items[0].string.string);
+ if(!strcmp(amfin->items[0].string.string, "_error"))
+ printamf(amfin);
+ }
+ if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "registered") && amfin->items[amfin->itemcount-1].type==AMF_STRING)
+ {
+ char* id=amfin->items[amfin->itemcount-1].string.string;
+ printf("Connection ID: %s\n", id);
+ char* key=getkey(id, channel);
- amfinit(&amf, 3);
- amfstring(&amf, "cauth");
- amfnum(&amf, 0);
- amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
- amfstring(&amf, key);
- amfsend(&amf, sock);
- free(key);
+ amfinit(&amf, 3);
+ amfstring(&amf, "cauth");
+ amfnum(&amf, 0);
+ amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
+ amfstring(&amf, key);
+ amfsend(&amf, sock);
+ free(key);
+ amfinit(&amf, 3);
+ amfstring(&amf, "nick");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, nickname);
+ amfsend(&amf, sock);
+ }
+ // Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
+ else if(amfin->itemcount>5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "privmsg") && amfin->items[3].type==AMF_STRING && amfin->items[4].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
+ {
+ size_t len;
+ char* msg=fromnumlist(amfin->items[3].string.string, &len);
+ const char* color=(showcolor?resolvecolor(amfin->items[4].string.string):"0");
+ printf("%s \x1b[%sm%s: ", timestamp(), color, amfin->items[5].string.string);
+ fwrite(msg, len, 1, stdout);
+ printf("\x1b[0m\n");
+ if(len==18 && !strncmp(msg, "/userinfo $request", 18))
+ {
+ char* msg;
+ if(account_user)
+ {
+ unsigned int len=strlen("/userinfo \n0")+strlen(account_user);
+ char buf[len+1];
+ sprintf(buf, "/userinfo %s\n", account_user);
+ msg=tonumlist(buf, len);
+ }else{
+ msg=tonumlist("/userinfo tc_client\n", 20); // TODO: include version number?
+ }
amfinit(&amf, 3);
- amfstring(&amf, "nick");
+ amfstring(&amf, "privmsg");
amfnum(&amf, 0);
amfnull(&amf);
- amfstring(&amf, nickname);
+ amfstring(&amf, msg);
+ amfstring(&amf, "#0,en");
+ int id=idlist_get(amfin->items[5].string.string);
+ char priv[snprintf(0, 0, "n%i-%s", id, amfin->items[5].string.string)+1];
+ sprintf(priv, "n%i-%s", id, amfin->items[5].string.string);
+ amfstring(&amf, priv);
amfsend(&amf, sock);
- }
- // Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
- else if(amfin->itemcount>5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "privmsg") && amfin->items[3].type==AMF_STRING && amfin->items[4].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
- {
- size_t len;
- char* msg=fromnumlist(amfin->items[3].string.string, &len);
- const char* color=(showcolor?resolvecolor(amfin->items[4].string.string):"0");
- printf("%s \x1b[%sm%s: ", timestamp(), color, amfin->items[5].string.string);
- fwrite(msg, len, 1, stdout);
- printf("\x1b[0m\n");
- if(len==18 && !strncmp(msg, "/userinfo $request", 18))
- {
- char* msg;
- if(account_user)
- {
- unsigned int len=strlen("/userinfo \n0")+strlen(account_user);
- char buf[len+1];
- sprintf(buf, "/userinfo %s\n", account_user);
- msg=tonumlist(buf, len);
- }else{
- msg=tonumlist("/userinfo tc_client\n", 20); // TODO: include version number?
- }
- amfinit(&amf, 3);
- amfstring(&amf, "privmsg");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, msg);
- amfstring(&amf, "#0,en");
- int id=idlist_get(amfin->items[5].string.string);
- char priv[snprintf(0, 0, "n%i-%s", id, amfin->items[5].string.string)+1];
- sprintf(priv, "n%i-%s", id, amfin->items[5].string.string);
- amfstring(&amf, priv);
- amfsend(&amf, sock);
- free(msg);
- }
free(msg);
- fflush(stdout);
}
- // users on channel entry. there's also a "joinsdone" command for some reason...
- else if(amfin->itemcount>3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "joins"))
+ free(msg);
+ fflush(stdout);
+ }
+ // users on channel entry. there's also a "joinsdone" command for some reason...
+ else if(amfin->itemcount>3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "joins"))
+ {
+ printf("Currently online: ");
+ int i;
+ for(i = 3; i < amfin->itemcount-1; i+=2)
{
- printf("Currently online: ");
- int i;
- for(i = 3; i < amfin->itemcount-1; i+=2)
+ // a "numeric" id precedes each nick, i.e. i is the id, i+1 is the nick
+ if(amfin->items[i].type==AMF_STRING && amfin->items[i+1].type==AMF_STRING)
{
- // a "numeric" id precedes each nick, i.e. i is the id, i+1 is the nick
- if(amfin->items[i].type==AMF_STRING && amfin->items[i+1].type==AMF_STRING)
- {
- idlist_add(atoi(amfin->items[i].string.string), amfin->items[i+1].string.string);
- printf("%s%s", (i==3?"":", "), amfin->items[i+1].string.string);
- }
+ idlist_add(atoi(amfin->items[i].string.string), amfin->items[i+1].string.string);
+ printf("%s%s", (i==3?"":", "), amfin->items[i+1].string.string);
}
- printf("\n");
- fflush(stdout);
}
- // join ("join", 0, "<ID>", "guest-<ID>")
- else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "join") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
- {
- idlist_add(atoi(amfin->items[2].string.string), amfin->items[3].string.string);
- printf("%s %s entered the channel\n", timestamp(), amfin->items[3].string.string);
- fflush(stdout);
- }
- // part
- else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "quit") && amfin->items[3].type==AMF_STRING)
+ printf("\n");
+ fflush(stdout);
+ }
+ // join ("join", 0, "<ID>", "guest-<ID>")
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "join") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
+ {
+ idlist_add(atoi(amfin->items[2].string.string), amfin->items[3].string.string);
+ printf("%s %s entered the channel\n", timestamp(), amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // quit/part
+ else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "quit") && amfin->items[2].type==AMF_STRING)
+ {
+ idlist_remove(amfin->items[2].string.string);
+ printf("%s %s left the channel\n", timestamp(), amfin->items[2].string.string);
+ fflush(stdout);
+ }
+ // nick
+ else if(amfin->itemcount==5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nick") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
+ {
+ if(!strcmp(amfin->items[2].string.string, nickname)) // Successfully changed our own nickname
{
- idlist_remove(amfin->items[2].string.string);
- printf("%s %s left the channel\n", timestamp(), amfin->items[2].string.string);
- fflush(stdout);
+ free(nickname);
+ nickname=strdup(amfin->items[3].string.string);
}
- // nick
- else if(amfin->itemcount==5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nick") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
+ idlist_rename(amfin->items[2].string.string, amfin->items[3].string.string);
+ printf("%s %s changed nickname to %s\n", timestamp(), amfin->items[2].string.string, amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // kick
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "kick") && amfin->items[2].type==AMF_STRING)
+ {
+ if(atoi(amfin->items[2].string.string) == idlist_get(nickname))
{
- if(!strcmp(amfin->items[2].string.string, nickname)) // Successfully changed our own nickname
- {
- free(nickname);
- nickname=strdup(amfin->items[3].string.string);
- }
- idlist_rename(amfin->items[2].string.string, amfin->items[3].string.string);
- printf("%s %s changed nickname to %s\n", timestamp(), amfin->items[2].string.string, amfin->items[3].string.string);
+ printf("%s You have been kicked out\n", timestamp());
fflush(stdout);
}
- // kick
- else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "kick") && amfin->items[2].type==AMF_STRING)
+ }
+ // banned
+ else if(amfin->itemcount==2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "banned"))
+ {
+ printf("%s You are banned from %s\n", timestamp(), channel);
+ fflush(stdout);
+ // When banned and reconnecting, tinychat doesn't disconnect us itself, we need to disconnect
+ close(sock);
+ return 1; // Getting banned is a failure, right?
+ }
+ // from_owner: notices
+ else if(amfin->itemcount==3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "from_owner") && amfin->items[2].type==AMF_STRING)
+ {
+ if(!strncmp("notice", amfin->items[2].string.string, 6))
{
- if(atoi(amfin->items[2].string.string) == idlist_get(nickname))
+ char* notice=strdup(&amfin->items[2].string.string[6]);
+ // replace "%20" with spaces
+ char* space;
+ while((space=strstr(notice, "%20")))
{
- printf("%s You have been kicked out\n", timestamp());
- fflush(stdout);
+ memmove(space, &space[2], strlen(&space[2])+1);
+ space[0]=' ';
}
- }
- // banned
- else if(amfin->itemcount==2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "banned"))
- {
- printf("%s You are banned from %s\n", timestamp(), channel);
+ printf("%s %s\n", timestamp(), notice);
fflush(stdout);
- // When banned and reconnecting, tinychat doesn't disconnect us itself, we need to disconnect
- close(sock);
- return 1; // Getting banned is a failure, right?
}
- // from_owner: notices
- else if(amfin->itemcount==3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "from_owner") && amfin->items[2].type==AMF_STRING)
+ }
+ // oper, identifies mods
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "oper") && amfin->items[3].type==AMF_STRING)
+ {
+ idlist_set_op(amfin->items[3].string.string, 1);
+ printf("%s is a moderator.\n", amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // deop, removes moderator privilege
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "deop") && amfin->items[3].type==AMF_STRING)
+ {
+ idlist_set_op(amfin->items[3].string.string, 0);
+ printf("%s is no longer a moderator.\n", amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // nickinuse, the nick we wanted to change to is already taken
+ else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nickinuse"))
+ {
+ printf("Nick is already in use.\n");
+ fflush(stdout);
+ }
+ // Room topic
+ else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "topic") && amfin->items[2].type==AMF_STRING && strlen(amfin->items[2].string.string) > 0)
+ {
+ printf("Room topic: %s\n", amfin->items[2].string.string);
+ fflush(stdout);
+ }
+ // Get list of banned users
+ else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "banlist"))
+ {
+ unsigned int i;
+ if(unban) // This is not a response to /banlist but to /forgive
{
- if(!strncmp("notice", amfin->items[2].string.string, 6))
+ for(i=2; i+1<amfin->itemcount; i+=2)
{
- char* notice=strdup(&amfin->items[2].string.string[6]);
- // replace "%20" with spaces
- char* space;
- while((space=strstr(notice, "%20")))
+ if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
+ if(!strcmp(amfin->items[i+1].string.string, unban))
{
- memmove(space, &space[2], strlen(&space[2])+1);
- space[0]=' ';
+ free(unban);
+ // A little unnecessary allocation, but the code gets cleaner without leaking
+ unban=strdup(amfin->items[i].string.string);
+ break;
}
- printf("%s %s\n", timestamp(), notice);
- fflush(stdout);
+ // If the nickname isn't found in the banlist we assume it's an ID
}
+ amfinit(&amf, 3);
+ amfstring(&amf, "forgive");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, unban);
+ amfsend(&amf, sock);
+ free(unban);
+ unban=0;
+ continue;
}
- // oper, identifies mods
- else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "oper") && amfin->items[3].type==AMF_STRING)
- {
- idlist_set_op(amfin->items[3].string.string, 1);
- printf("%s is a moderator.\n", amfin->items[3].string.string);
- fflush(stdout);
- }
- // deop, removes moderator privilege
- else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "deop") && amfin->items[3].type==AMF_STRING)
+ printf("Banned users:\n");
+ printf("ID Nickname\n");
+ for(i=2; i+1<amfin->itemcount; i+=2)
{
- idlist_set_op(amfin->items[3].string.string, 0);
- printf("%s is no longer a moderator.\n", amfin->items[3].string.string);
- fflush(stdout);
+ if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
+ unsigned int len=printf("%s", amfin->items[i].string.string);
+ for(;len<10; ++len){printf(" ");}
+ printf(" %s\n", amfin->items[i+1].string.string);
}
- // nickinuse, the nick we wanted to change to is already taken
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nickinuse"))
+ printf("Use /forgive <ID> to unban someone\n");
+ fflush(stdout);
+ }
+ // "avons", 0, "ID1", "nick1", "IDn", "nickn"...
+ else if(amfin->itemcount>1 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "avons"))
+ {
+ printf("Currently on cam: ");
+ int i;
+ // a "numeric" id precedes each nick, so we start on 3 and increment by 2
+ for(i = 3; i < amfin->itemcount; i+=2)
{
- printf("Nick is already in use.\n");
- fflush(stdout);
+ if(amfin->items[i].type==AMF_STRING)
+ {
+ printf("%s%s", (i==3?"":", "), amfin->items[i].string.string);
+ }
}
- // Room topic
- else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "topic") && amfin->items[2].type==AMF_STRING && strlen(amfin->items[2].string.string) > 0)
+ printf("\n");
+ fflush(stdout);
+ }
+ else if(amfin->itemcount>4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "notice") && amfin->items[2].type==AMF_STRING && amf_comparestrings_c(&amfin->items[2].string, "avon"))
+ {
+ if(amfin->items[4].type==AMF_STRING)
{
- printf("Room topic: %s\n", amfin->items[2].string.string);
+ printf("%s cammed up\n", amfin->items[4].string.string);
fflush(stdout);
}
-/* More not yet usable cam code, response to trying to open a new stream
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "_result"))
- {
- printamf(amfin);
- if(camid)
- {
- amfinit(&amf, 8);
- amfstring(&amf, "play");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, camid);
- amf.msgid=le32(1);
- amfsend(&amf, sock);
- }
- }
-*/
- // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
- amf_free(amfin);
}
- else{printf("Server disconnected\n"); break;}
+ // Handle results for various requests, haven't seen much of a pattern to it, always successful?
+ else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "_result"))
+ {
+ // Creating a new stream worked, now play media (cam/mic) on it (if that's what the result was for)
+ stream_play(amfin, sock);
+ }
+ // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
+ amf_free(amfin);
}
free(rtmp.buf);
close(sock);
diff --git a/configure b/configure
index d45512a..517c8ec 100755
--- a/configure
+++ b/configure
@@ -38,4 +38,44 @@ else
fi
rm -f iconvtest iconvtest.c
+printf 'Checking for gtk+-3.0... '
+libs="`pkg-config --libs gtk+-3.0`"
+if [ "x$libs" != "x" ]; then
+ echo "GTK_LIBS=${libs}" >> config.mk
+ echo "GTK_CFLAGS=`pkg-config --cflags gtk+-3.0`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
+printf 'Checking for libavcodec... '
+libs="`pkg-config --libs libavcodec`"
+if [ "x$libs" != "x" ]; then
+ echo "AVCODEC_LIBS=${libs}" >> config.mk
+ echo "AVCODEC_CFLAGS=`pkg-config --cflags libavcodec`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
+printf 'Checking for libswscale... '
+libs="`pkg-config --libs libswscale`"
+if [ "x$libs" != "x" ]; then
+ echo "SWSCALE_LIBS=${libs}" >> config.mk
+ echo "SWSCALE_CFLAGS=`pkg-config --cflags libswscale`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
+printf 'Checking for libavutil... '
+libs="`pkg-config --libs libavutil`"
+if [ "x$libs" != "x" ]; then
+ echo "AVUTIL_LIBS=${libs}" >> config.mk
+ echo "AVUTIL_CFLAGS=`pkg-config --cflags libavutil`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
echo Done
diff --git a/media.c b/media.c
index b224911..71d54e9 100644
--- a/media.c
+++ b/media.c
@@ -16,11 +16,19 @@
*/
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
+//#include "rtmp.h"
+//#include "amfparser.h"
#include "endian.h"
#include "media.h"
#include "amfwriter.h"
#include "idlist.h"
+/*
+struct stream
+{
+ unsigned int streamid;
+ unsigned int userid;
+};
+*/
struct stream* streams=0;
unsigned int streamcount=0;
@@ -35,16 +43,20 @@ char stream_idtaken(unsigned int id)
return 0;
}
-void stream_start(const char* nick, int sock) // called upon privmsg "/opencam ..."
+void stream_start(const char* nick, int sock) // called upon privmsg "/cam ..."
{
unsigned int userid=idlist_get(nick);
+/*
+ unsigned int id=idlist_get((char*)&buf[5]);
+ camid=malloc(128);
+ sprintf(camid, "%u", id);
+*/
unsigned int streamid=1;
while(stream_idtaken(streamid)){++streamid;}
++streamcount;
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");
@@ -52,26 +64,6 @@ void stream_start(const char* nick, int sock) // called upon privmsg "/opencam .
amfnull(&amf);
amfsend(&amf, sock);
printf("Starting media stream for %s (%u)\n", nick, userid);
- fflush(stdout);
-}
-
-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");
- fflush(stdout);
}
void stream_play(struct amf* amf, int sock) // called upon _result
@@ -83,13 +75,12 @@ void stream_play(struct amf* amf, int sock) // called upon _result
{
struct rtmp amf;
amfinit(&amf, 8);
- amfstring(&amf, streams[i].outgoing?"publish":"play");
+ amfstring(&amf, "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;
@@ -99,103 +90,16 @@ void stream_play(struct amf* amf, int sock) // called upon _result
void stream_handledata(struct rtmp* rtmp)
{
+rtmp->msgid=1;
unsigned int i;
for(i=0; i<streamcount; ++i)
{
if(streams[i].streamid!=rtmp->msgid){continue;}
-// fprintf(stderr, "Chunk: chunkid: %u, streamid: %u, userid: %u\n", rtmp->chunkid, rtmp->msgid, streams[i].userid);
- if(rtmp->type==RTMP_VIDEO)
- {
- printf("Video: %u %u\n", streams[i].userid, rtmp->length);
- }else if(rtmp->type==RTMP_AUDIO){
- printf("Audio: %u %u\n", streams[i].userid, rtmp->length);
- }
+ printf("Video: %u %u\n", streams[i].userid, rtmp->length); // TODO: if this becomes permanent we will have to specify a nick or ID or something
+// write(1, rtmp.buf, rtmp.length);
fwrite(rtmp->buf, rtmp->length, 1, stdout);
fflush(stdout);
return;
}
printf("Received media data to unknown stream ID %u\n", rtmp->msgid);
}
-
-void stream_handlestatus(struct amf* amf, int sock)
-{
- if(amf->itemcount<3 || amf->items[2].type!=AMF_OBJECT){return;}
- struct amfobject* obj=&amf->items[2].object;
- struct amfitem* code=amf_getobjmember(obj, "code");
- struct amfitem* details=amf_getobjmember(obj, "details");
- if(!code || !details){return;}
- if(code->type!=AMF_STRING || details->type!=AMF_STRING){return;}
- if(!strcmp(code->string.string, "NetStream.Play.Stop"))
- {
- unsigned int id=strtoul(details->string.string, 0, 0);
- unsigned int i;
- for(i=0; i<streamcount; ++i)
- {
- if(streams[i].userid==id)
- {
- printf("VideoEnd: %u\n", streams[i].userid);
- // Delete the stream
- struct rtmp amf;
- amfinit(&amf, 3);
- amfstring(&amf, "deleteStream");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfnum(&amf, streams[i].streamid);
- amfsend(&amf, sock);
- // Remove from list of streams
- --streamcount;
- memmove(&streams[i], &streams[i+1], sizeof(struct stream)*(streamcount-i));
- return;
- }
- }
- }
-}
-
-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;
- }
- }
-}
-
-void stream_stopvideo(int sock)
-{
- unsigned int i;
- for(i=0; i<streamcount; ++i)
- {
- if(streams[i].outgoing)
- {
- struct rtmp amf;
- // Close the stream
- amfinit(&amf, 8);
- amfstring(&amf, "closeStream");
- amfnum(&amf, 0);
- amfnull(&amf);
- amf.msgid=le32(streams[i].streamid);
- amfsend(&amf, sock);
- // Delete the stream
- amfinit(&amf, 3);
- amfstring(&amf, "deleteStream");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfnum(&amf, streams[i].streamid);
- amfsend(&amf, sock);
- // Remove from list of streams
- --streamcount;
- memmove(&streams[i], &streams[i+1], sizeof(struct stream)*(streamcount-i));
- return;
- }
- }
-}
diff --git a/media.h b/media.h
index 25195ab..58850d4 100644
--- a/media.h
+++ b/media.h
@@ -20,16 +20,11 @@ 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_start(const char* nick, int sock); // called upon privmsg "/cam ..."
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, int sock);
-extern void stream_sendvideo(int sock, void* buf, size_t len);
-extern void stream_stopvideo(int sock);
diff --git a/rtmp.c b/rtmp.c
index a1f5ac8..3c5c82b 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -21,84 +21,123 @@
#include "endian.h"
#include "rtmp.h"
+struct chunk
+{
+ unsigned int id;
+ unsigned int length;
+ unsigned char type;
+ unsigned int timestamp;
+ unsigned int streamid;
+ unsigned int pos;
+ void* buf;
+};
+struct chunk* chunks=0;
+unsigned int chunkcount=0;
+
+struct chunk* chunk_get(unsigned int id)
+{
+ unsigned int i;
+ for(i=0; i<chunkcount; ++i)
+ {
+ if(chunks[i].id==id){return &chunks[i];}
+ }
+// printf("No chunk found for %u, creating new one\n", id);
+ ++chunkcount;
+ chunks=realloc(chunks, sizeof(struct chunk)*chunkcount);
+ chunks[i].id=id;
+ chunks[i].streamid=0;
+ chunks[i].buf=0;
+ chunks[i].timestamp=0;
+ chunks[i].length=0;
+ return &chunks[i];
+}
+
char rtmp_get(int sock, struct rtmp* rtmp)
{
- static unsigned int length;
- static unsigned char type;
- static unsigned int timestamp;
- // Header format and stream ID
+ // Header format and chunk ID
unsigned int x=0;
if(read(sock, &x, 1)<1){return 0;}
- unsigned int streamid=x&0x3f;
+ unsigned int chunkid=x&0x3f;
unsigned int fmt=(x&0xc0)>>6;
- unsigned int msgid=0;
+ struct chunk* chunk=chunk_get(chunkid);
// Handle extended stream IDs
- if(streamid<2) // (0=1 extra byte, 1=2 extra bytes)
+ if(chunkid<2) // (0=1 extra byte, 1=2 extra bytes)
{
- read(sock, &x, streamid+1);
- streamid=64+x;
+ read(sock, &x, chunkid+1);
+ chunkid=64+x;
}
if(fmt<3)
{
// Timestamp
read(sock, &x, 3);
- timestamp=x;
+ chunk->timestamp=x;
if(fmt<2)
{
// Length
x=0;
read(sock, ((void*)&x)+1, 3);
- length=be32(x);
+ chunk->length=be32(x);
// Type
- read(sock, &type, sizeof(type));
+ read(sock, &chunk->type, sizeof(chunk->type));
if(fmt<1)
{
// Message ID
- read(sock, &msgid, sizeof(msgid));
+ read(sock, &chunk->streamid, sizeof(chunk->streamid));
}
}
}
// Extended timestamp
- if(timestamp==0xffffff)
+ if(chunk->timestamp==0xffffff)
{
read(sock, &x, sizeof(x));
- timestamp=be32(x);
+ chunk->timestamp=be32(x);
}
- rtmp->type=type;
- rtmp->streamid=streamid;
- rtmp->length=length;
- rtmp->msgid=le32(msgid);
- free(rtmp->buf);
- rtmp->buf=malloc(rtmp->length);
- size_t pos=0;
- size_t w;
- // Only read up to 128 bytes at a time and discard the (garbage/RTMP continuation header) bytes in between
- while(pos<length)
+ if(!chunk->buf)
+ {
+ chunk->buf=malloc(chunk->length);
+ chunk->pos=0;
+ }
+ unsigned int rsize=((chunk->length-chunk->pos>127)?128:(chunk->length-chunk->pos));
+ while(rsize>0)
+ {
+ size_t r=read(sock, chunk->buf+chunk->pos, rsize);;
+ if(r<1){return 0;}
+ rsize-=r;
+ chunk->pos+=r;
+// if(rsize){printf("Got a short read, %u remaining\n", rsize);}
+ }
+ if(chunk->pos==chunk->length)
{
- w=read(sock, rtmp->buf+pos, ((length-pos>127)?128:(length-pos)));
- if(w<1){break;}
- pos+=w;
- if(length-pos>0){read(sock, &w, 1);} // Skip junk once every 128 bytes
+// printf("Got chunk: chunkid=%u, type=%u, length=%u, streamid=%u\n", chunk->id, chunk->type, chunk->length, chunk->streamid);
+ rtmp->type=chunk->type;
+ rtmp->chunkid=chunk->id;
+ rtmp->length=chunk->length;
+ rtmp->msgid=le32(chunk->streamid);
+ free(rtmp->buf);
+ rtmp->buf=chunk->buf;
+ chunk->buf=0;
+ return 1;
}
- return 1;
+// printf("Waiting for next part of chunk\n");
+ return 2;
}
void rtmp_send(int sock, struct rtmp* rtmp)
{
// Header format and stream ID
unsigned int fmt=(rtmp->msgid?0:1);
- unsigned char basicheader=(rtmp->streamid<64?rtmp->streamid:(rtmp->streamid<256?0:1)) | (fmt<<6);
+ unsigned char basicheader=(rtmp->chunkid<64?rtmp->chunkid:(rtmp->chunkid<256?0:1)) | (fmt<<6);
write(sock, &basicheader, sizeof(basicheader));
- if(rtmp->streamid>=64) // Handle large stream IDs
+ if(rtmp->chunkid>=64) // Handle large stream IDs
{
- if(rtmp->streamid<256)
+ if(rtmp->chunkid<256)
{
- unsigned char streamid=rtmp->streamid-64;
- write(sock, &streamid, sizeof(streamid));
+ unsigned char chunkid=rtmp->chunkid-64;
+ write(sock, &chunkid, sizeof(chunkid));
}else{
- unsigned short streamid=le16(rtmp->streamid-64);
- write(sock, &streamid, sizeof(streamid));
+ unsigned short chunkid=le16(rtmp->chunkid-64);
+ write(sock, &chunkid, sizeof(chunkid));
}
}
unsigned int x=0;
diff --git a/rtmp.h b/rtmp.h
index f18b2ce..6c03274 100644
--- a/rtmp.h
+++ b/rtmp.h
@@ -27,7 +27,7 @@
struct rtmp
{
unsigned char type;
- unsigned int streamid;
+ unsigned int chunkid;
unsigned int length;
unsigned int msgid;
void* buf;
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index 174c60a..51e6738 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -16,525 +16,110 @@
*/
#include <unistd.h>
#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/prctl.h>
#include <libavcodec/avcodec.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
-#ifdef HAVE_SOUND
-// TODO: use libavresample instead if available
- #if HAVE_SOUND==avresample
- #include <libavutil/opt.h>
- #include <libavresample/avresample.h>
- #else
- #include <libswresample/swresample.h>
- #endif
- #include <ao/ao.h>
-#endif
#include <gtk/gtk.h>
-#ifdef HAVE_V4L2
- #include <libv4l2.h>
- #include <linux/videodev2.h>
-#endif
-#if GTK_MAJOR_VERSION==2
- #define GTK_ORIENTATION_HORIZONTAL 0
- #define GTK_ORIENTATION_VERTICAL 1
- GtkWidget* gtk_box_new(int vertical, int spacing)
- {
- if(vertical)
- {
- return gtk_vbox_new(1, spacing);
- }else{
- return gtk_hbox_new(1, spacing);
- }
- }
-#endif
-
-struct camera
+struct viddata
{
AVFrame* frame;
AVFrame* dstframe;
GtkWidget* cam;
- AVCodecContext* vctx;
- AVCodecContext* actx;
- short* samples;
- unsigned int samplecount;
- char* id;
- char* nick;
- GtkWidget* box; // holds label and cam
-};
-
-struct viddata
-{
- struct camera* cams;
- unsigned int camcount;
- GtkWidget* box;
- AVCodec* vdecoder;
- AVCodec* vencoder;
- AVCodec* adecoder;
-#ifdef HAVE_SOUND
- int audiopipe;
- #if HAVE_SOUND==avresample
- AVAudioResampleContext* resamplectx;
- #else
- SwrContext* swrctx;
- #endif
-#endif
+ AVCodecContext* ctx;
+ char* camnick;
};
int tc_client[2];
int tc_client_in[2];
-#ifdef HAVE_SOUND
-// Experimental mixer, not sure if it really works
-void camera_playsnd(struct viddata* data, struct camera* cam, short* samples, unsigned int samplecount)
-{
- if(cam->samples)
- {
-// int sources=1;
- unsigned int i;
- for(i=0; i<data->camcount; ++i)
- {
- if(!data->cams[i].samples){continue;}
- if(cam==&data->cams[i]){continue;}
- unsigned j;
- for(j=0; j<cam->samplecount && j<data->cams[i].samplecount; ++j)
- {
- cam->samples[j]+=data->cams[i].samples[j];
- }
- free(data->cams[i].samples);
- data->cams[i].samples=0;
-// ++sources;
- }
- write(data->audiopipe, cam->samples, cam->samplecount*sizeof(short));
- free(cam->samples);
-// printf("Mixed sound from %i sources (cam: %p)\n", sources, cam);
- }
- cam->samples=malloc(samplecount*sizeof(short));
- memcpy(cam->samples, samples, samplecount*sizeof(short));
- cam->samplecount=samplecount;
-}
-#endif
-
-void camera_remove(struct viddata* data, const char* nick)
-{
- unsigned int i;
- for(i=0; i<data->camcount; ++i)
- {
- if(!strcmp(data->cams[i].id, nick))
- {
- gtk_widget_destroy(data->cams[i].box);
- av_frame_free(&data->cams[i].frame);
- avcodec_free_context(&data->cams[i].vctx);
-#ifdef HAVE_SOUND
- avcodec_free_context(&data->cams[i].actx);
-#endif
- free(data->cams[i].id);
- free(data->cams[i].nick);
- --data->camcount;
- memmove(&data->cams[i], &data->cams[i+1], (data->camcount-i)*sizeof(struct camera));
- break;
- }
- }
-}
-
char buf[1024];
gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
struct viddata* data=datap;
- unsigned int i;
+ int i;
for(i=0; i<1023; ++i)
{
if(read(tc_client[0], &buf[i], 1)<1){printf("No more data\n"); gtk_main_quit(); return 0;}
if(buf[i]=='\r'||buf[i]=='\n'){break;}
}
buf[i]=0;
- // Start streams once we're properly connected
- if(!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], "/opencam %s\n", user);
- }
- return 1;
- }
- char* space=strchr(buf, ' ');
- // Start a stream when someone cams up
- if(space && !strcmp(space, " cammed up"))
- {
- space[0]=0;
- dprintf(tc_client_in[1], "/opencam %s\n", buf);
- return 1;
- }
- // Make sure the cam goes away when a user leaves
- else if(space && !strcmp(space, " left the channel"))
- {
- space[0]=0;
- camera_remove(data, buf);
- 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;
- ++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(nick);
- cam->id=strdup(id);
- cam->vctx=avcodec_alloc_context3(data->vdecoder);
- avcodec_open2(cam->vctx, data->vdecoder, 0);
-#ifdef HAVE_SOUND
- cam->actx=avcodec_alloc_context3(data->adecoder);
- avcodec_open2(cam->actx, data->adecoder, 0);
- cam->samples=0;
-#endif
- 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(!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))
- {
- camera_remove(data, &buf[10]);
- 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;
- read(tc_client[0], &frameinfo, 1);
- --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)
- {
- pos+=read(tc_client[0], pkt.data+pos, size-pos);
- }
-#ifdef HAVE_SOUND
- // Find the camera representation for the given ID (for decoder context)
- struct camera* cam=0;
- for(i=0; i<data->camcount; ++i)
- {
- 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]); return 1;}
- int gotframe;
- avcodec_decode_audio4(cam->actx, cam->frame, &gotframe, &pkt);
- if(!gotframe){return 1;}
- #if HAVE_SOUND==avresample
- int outlen=avresample_convert(data->resamplectx, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples);
- #else
- int outlen=swr_convert(data->resamplectx, cam->frame->data, cam->frame->nb_samples, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
- #endif
- camera_playsnd(data, cam, (short*)cam->frame->data[0], outlen);
-#endif
- return 1;
- }
- if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); fflush(stdout); return 1;} // Ignore anything else that isn't video
+ // Start stream once we're properly connected
+ if(!strncmp(buf, "Connection ID: ", 10)){dprintf(tc_client_in[1], "/opencam %s\n", data->camnick); return 1;}
+ if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); return 1;} // Ignore anything that isn't video
char* sizestr=strchr(&buf[7], ' ');
- if(!sizestr){return 1;}
- sizestr[0]=0;
- // Find the camera representation for the given ID
- struct camera* cam=0;
- for(i=0; i<data->camcount; ++i)
- {
- if(!strcmp(data->cams[i].id, &buf[7])){cam=&data->cams[i]; break;}
- }
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)
+ // Mostly ignore the first byte (contains frame type (e.g. keyframe etc.) in 4 bits and codec in the other 4, but we assume FLV1)
--size;
AVPacket pkt;
av_init_packet(&pkt);
- unsigned char databuf[size+4];
- pkt.data=databuf;
- unsigned char frameinfo;
- read(tc_client[0], &frameinfo, 1);
-// printf("Frametype-frame: %x\n", ((unsigned int)frameinfo&0xf0)/16);
-// printf("Frametype-codec: %x\n", (unsigned int)frameinfo&0xf);
- unsigned int pos=0;
- while(pos<size)
+ pkt.data=malloc(size);
+ read(tc_client[0], pkt.data, 1); // Skip
+// printf("Frametype-frame: %x\n", ((unsigned int)pkt.data[0]&0xf0)/16);
+// printf("Frametype-codec: %x\n", (unsigned int)pkt.data[0]&0xf);
+ if((pkt.data[0]&0xf)!=2) // Not FLV1, get data but discard it
{
- pos+=read(tc_client[0], pkt.data+pos, size-pos);
+ read(tc_client[0], pkt.data, size);
+ return 1;
}
- 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]); return 1;}
+ read(tc_client[0], pkt.data, size);
pkt.size=size;
int gotframe;
- avcodec_decode_video2(cam->vctx, cam->frame, &gotframe, &pkt);
+ avcodec_decode_video2(data->ctx, data->frame, &gotframe, &pkt);
+ free(pkt.data);
if(!gotframe){return 1;}
// Convert to RGB24 format
- unsigned int bufsize=avpicture_get_size(PIX_FMT_RGB24, cam->frame->width, cam->frame->height);
+ unsigned int bufsize=avpicture_get_size(PIX_FMT_RGB24, data->frame->width, data->frame->height);
unsigned char buf[bufsize];
- cam->dstframe->data[0]=buf;
- cam->dstframe->linesize[0]=cam->frame->width*3;
- struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, cam->frame->width, cam->frame->height, AV_PIX_FMT_RGB24, 0, 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);
-
- GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, cam->frame->width, cam->frame->height, cam->dstframe->linesize[0], 0, 0);
- gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
- // Make sure it gets redrawn in time
- gdk_window_process_updates(gtk_widget_get_window(cam->cam), 1);
+ data->dstframe->data[0]=buf;
+ data->dstframe->linesize[0]=data->frame->width*3;
+ struct SwsContext* swsctx=sws_getContext(data->frame->width, data->frame->height, data->frame->format, data->frame->width, data->frame->height, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
+ sws_scale(swsctx, (const uint8_t*const*)data->frame->data, data->frame->linesize, 0, data->frame->height, data->dstframe->data, data->dstframe->linesize);
+ GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(data->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, data->frame->width, data->frame->height, data->dstframe->linesize[0], 0, 0);
+ gtk_image_set_from_pixbuf(GTK_IMAGE(data->cam), gdkframe);
g_object_unref(gdkframe);
- return 1;
-}
-
-#ifdef HAVE_SOUND
-void audiothread(int fd)
-{
- ao_initialize();
- ao_sample_format samplefmt;
- samplefmt.bits=16;
- samplefmt.rate=22050;
- samplefmt.channels=1;
- samplefmt.byte_format=AO_FMT_NATIVE; // I'm guessing libavcodec decodes it to native
- samplefmt.matrix=0;
- ao_option clientname={.key="client_name", .value="tc_client/camviewer", .next=0};
- ao_device* dev=ao_open_live(ao_default_driver_id(), &samplefmt, &clientname);
- char buf[2048];
- size_t len;
- while((len=read(fd, buf, 2048))>0)
- {
- ao_play(dev, buf, len);
- }
- ao_close(dev);
-}
-#endif
-#ifdef HAVE_V4L2
-pid_t camproc=0;
-void togglecam(GtkButton* button, struct viddata* data)
-{
- if(camproc)
- {
- kill(camproc, SIGINT);
- camproc=0;
- gtk_button_set_label(button, "Broadcast cam");
- dprintf(tc_client_in[1], "/camdown\n");
- dprintf(tc_client[1], "VideoEnd: out\n"); // Close our local display
- return;
- }
- // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
- int campipe[2];
- pipe(campipe);
- dprintf(tc_client_in[1], "/camup\n");
- gtk_button_set_label(button, "Stop broadcasting");
-// printf("Camming up!\n");
- camproc=fork();
- if(!camproc)
- {
- close(campipe[0]);
- 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=0x22; // Note: differentiating between keyframes and non-keyframes seems to break stuff, so let's just go with all being interframes (1=keyframe, 2=interframe, 3=disposable interframe)
- 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(campipe[1], "Video: out %i\n", packet.size+1);
- write(campipe[1], &frameinfo, 1);
- write(campipe[1], packet.data, packet.size);
-
- av_free_packet(&packet);
- }
- sws_freeContext(swsctx);
- _exit(0);
- }
- close(campipe[1]);
- GIOChannel* channel=g_io_channel_unix_new(campipe[0]);
- g_io_channel_set_encoding(channel, 0, 0);
- g_io_add_watch(channel, G_IO_IN, handledata, data);
+ return 1;
}
-#endif
int main(int argc, char** argv)
{
- struct viddata data={0,0,0,0,0};
+ struct viddata data={av_frame_alloc(),av_frame_alloc()};
+ data.camnick=argv[1];
+ // Init the decoder
avcodec_register_all();
- data.vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
- data.adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
-
-#ifdef HAVE_SOUND
- #if HAVE_SOUND==avresample
- data.resamplectx=avresample_alloc_context();
- av_opt_set_int(data.resamplectx, "in_channel_layout", AV_CH_FRONT_CENTER, 0);
- av_opt_set_int(data.resamplectx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
- // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
- av_opt_set_int(data.resamplectx, "in_sample_rate", 11025, 0);
- av_opt_set_int(data.resamplectx, "out_channel_layout", AV_CH_FRONT_CENTER, 0);
- av_opt_set_int(data.resamplectx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
- av_opt_set_int(data.resamplectx, "out_sample_rate", 22050, 0);
- avresample_open(data.resamplectx);
- #else
- data.resamplectx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0);
- swr_init(data.swrctx);
- #endif
- int audiopipe[2];
- pipe(audiopipe);
- data.audiopipe=audiopipe[1];
- if(!fork())
- {
- prctl(PR_SET_PDEATHSIG, SIGHUP);
- close(audiopipe[1]);
- audiothread(audiopipe[0]);
- _exit(0);
- }
- close(audiopipe[0]);
-#endif
+ AVCodec* decoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
+ data.ctx=avcodec_alloc_context3(decoder);
+ avcodec_open2(data.ctx, decoder, 0);
gtk_init(&argc, &argv);
GtkWidget* w=gtk_window_new(GTK_WINDOW_TOPLEVEL);
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(togglecam), &data);
- gtk_box_pack_start(GTK_BOX(data.box), cambutton, 0, 0, 0);
-#endif
+ data.cam=gtk_image_new();
+ gtk_container_add(GTK_CONTAINER(w), data.cam);
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);
dup2(tc_client_in[0], 0);
- argv[0]="./tc_client";
- execv("./tc_client", argv);
+ argv[1]="./tc_client";
+ execv("./tc_client", &argv[1]);
}
+ 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);
- unsigned int channel_id=g_io_add_watch(tcchannel, G_IO_IN, handledata, &data);
+ g_io_add_watch(tcchannel, G_IO_IN, handledata, &data);
gtk_main();
-
- g_source_remove(channel_id);
- g_io_channel_shutdown(tcchannel, 0, 0);
- unsigned int i;
- for(i=0; i<data.camcount; ++i)
- {
- av_frame_free(&data.cams[i].frame);
- avcodec_free_context(&data.cams[i].vctx);
-#ifdef HAVE_SOUND
- avcodec_free_context(&data.cams[i].actx);
- #if HAVE_SOUND==avresample
- avresample_free(&data.resamplectx);
- #else
- swr_free(&data.swrctx);
- #endif
-#endif
- free(data.cams[i].id);
- free(data.cams[i].nick);
- }
- free(data.cams);
+
+ free(data.frame->data[0]);
+ av_frame_free(&data.frame);
return 0;
}
diff --git a/utilities/compat.c b/utilities/compat.c
deleted file mode 100644
index e620559..0000000
--- a/utilities/compat.c
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- 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
deleted file mode 100644
index 2cfb39b..0000000
--- a/utilities/compat.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- 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/buffer.c b/utilities/cursedchat/buffer.c
deleted file mode 100644
index af06a16..0000000
--- a/utilities/cursedchat/buffer.c
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- cursedchat, a simple curses interface for tc_client
- 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 <string.h>
-#include <stdlib.h>
-#include <curses.h>
-#include "buffer.h"
-
-struct buffer* buffers=0;
-unsigned int buffercount=0;
-unsigned int currentbuf=0;
-
-unsigned int createbuffer(const char* name)
-{
- ++buffercount;
- buffers=realloc(buffers, buffercount*sizeof(struct buffer));
- buffers[buffercount-1].pad=newpad(2048, COLS);
- scrollok(buffers[buffercount-1].pad, 1);
- buffers[buffercount-1].name=(name?strdup(name):0);
- buffers[buffercount-1].scroll=-1;
- buffers[buffercount-1].seen=1;
- return buffercount-1;
-}
-
-unsigned int findbuffer(const char* name)
-{
- unsigned int i;
- for(i=1; i<buffercount; ++i)
- {
- if(!strcmp(buffers[i].name, name)){return i;}
- }
- return 0;
-}
-
-void renamebufferunique(unsigned int id)
-{
- unsigned int i=0;
- while(i<buffercount)
- {
- int len=strlen(buffers[id].name);
- buffers[id].name=realloc(buffers[id].name, len+2);
- buffers[id].name[len]='_';
- buffers[id].name[len+1]=0;
- for(i=1; i<buffercount; ++i)
- {
- if(i!=id && !strcmp(buffers[i].name, buffers[id].name)){break;}
- }
- }
-}
diff --git a/utilities/cursedchat/buffer.h b/utilities/cursedchat/buffer.h
deleted file mode 100644
index 79ec1fc..0000000
--- a/utilities/cursedchat/buffer.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- cursedchat, a simple curses interface for tc_client
- 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/>.
-*/
-struct buffer
-{
- WINDOW* pad;
- char* name;
- int scroll;
- char seen;
-};
-
-extern struct buffer* buffers;
-extern unsigned int buffercount;
-extern unsigned int currentbuf;
-
-extern unsigned int createbuffer(const char* name);
-extern unsigned int findbuffer(const char* name);
-extern void renamebufferunique(unsigned int id);
diff --git a/utilities/cursedchat/cursedchat.c b/utilities/cursedchat/cursedchat.c
deleted file mode 100644
index 6e5385c..0000000
--- a/utilities/cursedchat/cursedchat.c
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- cursedchat, a simple curses interface for tc_client
- Copyright (C) 2015 alicia@ion.nu
- Copyright (C) 2015 Pamela Hiatt
-
- 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 <unistd.h>
-#include <stdlib.h>
-#include <poll.h>
-#include <signal.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-#include <locale.h>
-#include <curses.h>
-#include <readline/readline.h>
-#include <readline/history.h>
-#include "../compat.h"
-#include "../list.h"
-#include "buffer.h"
-
-#define HALFSCREEN (LINES>4?(LINES-3)/2:1)
-
-WINDOW* topic;
-char* channeltopic;
-WINDOW* input;
-int to_app;
-struct list userlist={0,0};
-char* nickname=0;
-
-// Translate ANSI escape codes to curses commands and write the text to a window
-void waddansi(WINDOW* w, char* str)
-{
- while(str[0])
- {
- char* esc=strstr(str, "\x1b[");
- if(esc==str)
- {
- str=&str[2];
- while(str[0]!='m')
- {
- if(str[0]=='3'&&str[1]!='m') // Color
- {
- unsigned int c=strtoul(&str[1], &str, 10);
- wattron(w, COLOR_PAIR(c+1));
- }
- else if(str[0]=='1') // Bold
- {
- wattron(w, A_BOLD);
- str=&str[1];
- }
- else if(str[0]=='0') // Reset
- {
- wattroff(w, COLOR_PAIR(1));
- wattroff(w, A_BOLD);
- str=&str[1];
- }
- else{str=&str[1];}
- }
- str=&str[1];
- continue;
- }
- if(esc)
- {
- waddnstr(w, str, esc-str);
- str=esc;
- }else{
- waddstr(w, str);
- return;
- }
- }
-}
-
-void drawchat(void)
-{
- WINDOW* w=buffers[currentbuf].pad;
- int scroll=buffers[currentbuf].scroll;
- prefresh(w, (scroll>-1?scroll:getcury(w)-LINES+4), 0, 1, 0, LINES-3, COLS);
-}
-
-void drawtopic(void)
-{
- werase(topic);
- unsigned int i;
- for(i=1; i<buffercount && buffers[i].seen; ++i);
- if(i<buffercount)
- {
- waddstr(topic, "Unread PMs from: ");
- char first=1;
- for(i=1; i<buffercount; ++i)
- {
- if(!buffers[i].seen)
- {
- if(first){first=0;}else{waddstr(topic, ", ");}
- waddstr(topic, buffers[i].name);
- }
- }
- }
- else if(currentbuf)
- {
- waddstr(topic, "To return to public chat type: /pm");
- }else{
- waddstr(topic, channeltopic);
- }
- wrefresh(topic);
-}
-
-void gotline(char* line)
-{
- if(!line){close(to_app); return;} // TODO: handle EOF on stdin better?
- add_history(line);
- if(!strcmp(line, "/pm"))
- {
- currentbuf=0;
- drawchat();
- drawtopic();
- return;
- }
- else if(!strncmp(line, "/pm ", 4))
- {
- currentbuf=findbuffer(&line[4]);
- if(!currentbuf){currentbuf=createbuffer(&line[4]);}
- buffers[currentbuf].seen=1;
- drawchat();
- drawtopic();
- return;
- }
- else if(!strncmp(line, "/buffer ", 8))
- {
- unsigned int num=atoi(&line[8]);
- if(num<0 || num>=buffercount)
- {
- wprintw(buffers[currentbuf].pad, "\nInvalid buffer number: %u", num);
- }else{
- currentbuf=num;
- buffers[currentbuf].seen=1;
- drawtopic();
- }
- drawchat();
- return;
- }
- else if(!strcmp(line, "/bufferlist"))
- {
- unsigned int i;
- for(i=0; i<buffercount; ++i)
- {
- wprintw(buffers[currentbuf].pad, "\n% 3i: %s", i, i?buffers[i].name:"");
- }
- drawchat();
- return;
- }
- else if(!strncmp(line, "/msg ", 5))
- {
- char* name=&line[5];
- char* msg=strchr(name, ' ');
- if(!msg){return;}
- msg[0]=0;
- currentbuf=findbuffer(name);
- if(!currentbuf){currentbuf=createbuffer(name);}
- buffers[currentbuf].seen=1;
- drawtopic();
- memmove(line, &msg[1], strlen(&msg[1])+1);
- }
- else if(!strcmp(line, "/help"))
- {
- waddstr(buffers[0].pad, "\nFor cursedchat:\n"
- "/pm <name> = switch to the PM buffer for <name>\n"
- "/pm = return to the channel/public chat's buffer\n"
- "/buffer <num> = switch to buffer by number\n"
- "/bufferlist = list open buffers, their numbers and associated names\n"
- "\nFor tc_client (through cursedchat):");
- write(to_app, line, strlen(line));
- write(to_app, "\n", 1);
- return;
- }
-
- if(currentbuf) // We're in a PM window, make the message a PM
- {
- dprintf(to_app, "/msg %s ", buffers[currentbuf].name);
- }
- write(to_app, line, strlen(line));
- write(to_app, "\n", 1);
- time_t timestamp=time(0);
- struct tm* t=localtime(×tamp);
- wprintw(buffers[currentbuf].pad, "\n[%02i:%02i] %s: %s", t->tm_hour, t->tm_min, nickname, line);
- drawchat();
-}
-
-unsigned int bytestochars(const char* buf, unsigned int buflen, unsigned int bytes)
-{
- unsigned int pos=0;
- unsigned int i;
- for(i=0; i<bytes; ++pos)
- {
- i+=mbtowc(0,&buf[i],buflen-i);
- }
- return pos;
-}
-
-unsigned int charstobytes(const char* buf, unsigned int buflen, unsigned int chars)
-{
- unsigned int pos;
- unsigned int i=0;
- for(pos=0; pos<chars; ++pos)
- {
- i+=mbtowc(0,&buf[i],buflen-i);
- }
- return i;
-}
-
-int escinput(int a, int byte)
-{
- char buf[4];
- read(0, buf, 2);
- buf[2]=0;
- if(!strcmp(buf, "[A")||!strcmp(buf, "OA")){rl_get_previous_history(1,27);return 0;}
- if(!strcmp(buf, "[B")||!strcmp(buf, "OB")){rl_get_next_history(1,27);return 0;}
- if(!strcmp(buf, "[C")||!strcmp(buf, "OC")){rl_forward(1,27);return 0;}
- if(!strcmp(buf, "[D")||!strcmp(buf, "OD")){rl_backward(1,27);return 0;}
- if(!strcmp(buf, "[H")||!strcmp(buf, "OH")){rl_beg_of_line(1,27);return 0;}
- if(!strcmp(buf, "[F")||!strcmp(buf, "OF")){rl_end_of_line(1,27);return 0;}
- if(!strcmp(buf, "[3")&&read(0, buf, 1)&&buf[0]=='~'){rl_delete(1,27);return 0;}
- if(!strcmp(buf, "[5")) // Page up
- {
- read(0, buf, 1);
- struct buffer* b=&buffers[currentbuf];
- if(b->scroll<0){b->scroll=getcury(b->pad)-LINES+4;}
- b->scroll-=HALFSCREEN;
- if(b->scroll<0){b->scroll=0;}
- drawchat();
- return 0;
- }
- if(!strcmp(buf, "[6")) // Page down
- {
- read(0, buf, 1);
- struct buffer* b=&buffers[currentbuf];
- if(b->scroll<0){return 0;} // Already at the bottom
- b->scroll+=HALFSCREEN;
- if(b->scroll>getcury(b->pad)-LINES+3){b->scroll=-1;}
- drawchat();
- return 0;
- }
- return 0;
-}
-
-void drawinput(void)
-{
- werase(input);
- unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
-
- waddstr(input, "> ");
- int cursor_row=(pos+2)/COLS;
- int end_row=(rl_end+2)/COLS;
- // Figure out how much of the buffer to print to not scroll past the cursor
- unsigned int eol=charstobytes(rl_line_buffer, rl_end, (cursor_row+2)*COLS-3); // -2 for cursor, -1 to avoid wrapping
- waddnstr(input, rl_line_buffer, eol);
-
- wmove(input, cursor_row==end_row && cursor_row>0, (pos+2)%COLS); // +2 for prompt
- wrefresh(input);
-}
-
-void resizechat(int sig)
-{
- struct winsize size;
- ioctl(0, TIOCGWINSZ, &size);
- if(size.ws_row<3){return;} // Too small, would result in negative numbers breaking the chat window
- resize_term(size.ws_row, size.ws_col);
- clear();
- refresh();
- wresize(topic, 1, COLS);
- unsigned int i;
- for(i=0; i<buffercount; ++i)
- {
- wresize(buffers[i].pad, buffers[i].pad->_maxy+1, COLS);
- }
- wresize(input, 2, COLS);
- mvwin(input, LINES-2, 0);
- redrawwin(buffers[currentbuf].pad);
- redrawwin(topic);
- redrawwin(input);
- drawchat();
- drawtopic();
- 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;}
- setlocale(LC_ALL, "");
- WINDOW* w=initscr();
- signal(SIGWINCH, resizechat);
- start_color();
- cbreak();
- noecho();
- keypad(w, 1);
- use_default_colors();
- topic=newwin(1, COLS, 0, 0);
- init_pair(1, COLOR_WHITE, COLOR_BLUE);
-
- // Define colors mapped to ANSI color codes (at least the ones tc_client uses)
- init_pair(2, COLOR_RED, -1);
- init_pair(3, COLOR_GREEN, -1);
- init_pair(4, COLOR_YELLOW, -1);
- init_pair(5, COLOR_BLUE, -1);
- init_pair(6, COLOR_MAGENTA, -1);
- init_pair(7, COLOR_CYAN, -1);
-
- wbkgd(topic, COLOR_PAIR(1)|' ');
- createbuffer(0);
- input=newwin(2, COLS, LINES-2, 0);
- scrollok(input, 1);
- 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);
- int app_in[2];
- int app_out[2];
- pipe(app_in);
- pipe(app_out);
- if(!fork())
- {
- close(app_in[1]);
- close(app_out[0]);
- dup2(app_in[0],0);
- dup2(app_out[1],1);
- argv[0]="./tc_client";
- execv("./tc_client", argv);
- _exit(1);
- }
- close(app_in[0]);
- close(app_out[1]);
- to_app=app_in[1];
- struct pollfd p[2]={{.fd=0, .events=POLLIN, .revents=0},
- {.fd=app_out[0], .events=POLLIN, .revents=0}};
- while(1)
- {
- poll(p, 2, -1);
- if(p[1].revents) // Getting data from tc_client
- {
- p[1].revents=0;
- char buf[1024];
- size_t len=0;
- while(len<1023)
- {
- if(read(app_out[0], &buf[len], 1)!=1){len=-1; break;}
- if(buf[len]=='\r'||buf[len]=='\n'){break;}
- ++len;
- }
- if(len==-1){break;} // Bad read
- buf[len]=0;
- unsigned int buffer=0;
- if(!strncmp(buf, "Room topic: ", 12))
- {
- free(channeltopic);
- channeltopic=strdup(&buf[12]);
- drawtopic();
- }
- else if(!strncmp(buf, "Connection ID: ", 15)) // Our initial nickname is "guest-" plus our connection ID
- {
- unsigned int length=strlen(&buf[15]);
- nickname=malloc(length+strlen("guest-")+1);
- sprintf(nickname, "guest-%s", &(buf[15]));
- }
- 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];
- char* msg=strchr(nick, ' ');
- if(msg[-1]==':')
- {
- nick=strchr(nick, 'm')+1;
- char* nickend=&msg[-1];
- msg=&msg[1];
- if(!strncmp(msg, "/msg ", 5)) // message is a PM
- {
- char* pm=strchr(&msg[5], ' ');
- if(!pm){waddstr(buffers[0].pad, "\npm is null!"); continue;}
- pm=&pm[1];
- nickend[0]=0;
- buffer=findbuffer(nick);
- if(!buffer){buffer=createbuffer(nick);}
- nickend[0]=':';
- memmove(msg, pm, strlen(pm)+1);
- if(buffer!=currentbuf)
- {
- buffers[buffer].seen=0;
- drawtopic();
- }
- }
- }
- else if(!strncmp(msg, " changed nickname to ", 21))
- {
- msg[0]=0;
- // Update name in userlist
- list_switch(&userlist, nick, &msg[21]);
- // If it was us, keep track of the new nickname
- if(!strcmp(nickname, nick))
- {
- free(nickname);
- nickname=strdup(&msg[21]);
- }
- // Prevent duplicate names for buffers, and all the issues that would bring
- unsigned int i;
- if((i=findbuffer(&msg[21])))
- {
- renamebufferunique(i);
- }
- for(i=1; i<buffercount; ++i)
- {
- if(!strcmp(buffers[i].name, nick))
- {
- free(buffers[i].name);
- buffers[i].name=strdup(&msg[21]);
- }
- }
- 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);
- drawchat();
- wrefresh(input);
- continue;
- }
- if(!p[0].revents){continue;}
- p[0].revents=0;
- rl_callback_read_char();
- drawinput();
- }
- rl_callback_handler_remove();
- endwin();
- return 0;
-}
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
deleted file mode 100644
index dd65a8d..0000000
--- a/utilities/gtk/camviewer.c
+++ /dev/null
@@ -1,888 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <unistd.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/prctl.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <libavcodec/avcodec.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
-#ifdef HAVE_SOUND
- // Use libavresample if available, otherwise fall back on libswresample
- #if HAVE_SOUND==avresample
- #include <libavutil/opt.h>
- #include <libavresample/avresample.h>
- #else
- #include <libswresample/swresample.h>
- #endif
- #include <ao/ao.h>
-#endif
-#include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
-#ifdef HAVE_V4L2
- #include <libv4l2.h>
- #include <linux/videodev2.h>
-#endif
-#include "userlist.h"
-#include "media.h"
-#include "compat.h"
-#include "config.h"
-#include "gui.h"
-#include "logging.h"
-#include "../stringutils.h"
-
-struct viddata
-{
- GtkWidget* box;
- AVCodec* vdecoder;
- AVCodec* vencoder;
- AVCodec* adecoder;
- int scalewidth;
- int scaleheight;
-#ifdef HAVE_SOUND
- int audiopipe;
- #if HAVE_SOUND==avresample
- AVAudioResampleContext* resamplectx;
- #else
- SwrContext* swrctx;
- #endif
-#endif
- GtkTextBuffer* buffer; // TODO: struct buffer array, for PMs
- GtkAdjustment* scroll;
- GtkBuilder* gui;
-};
-
-int tc_client[2];
-int tc_client_in[2];
-const char* channel=0;
-const char* mycolor=0;
-
-void updatescaling(struct viddata* data, unsigned int width, unsigned int height)
-{
-// TODO: Move updatescaling into media.c?
- if(!camcount){return;}
- if(!width){width=gtk_widget_get_allocated_width(data->box);}
- if(!height){height=gtk_widget_get_allocated_height(data->box);}
- data->scalewidth=width/camcount;
- // 3/4 ratio
- data->scaleheight=data->scalewidth*3/4;
- unsigned int i;
- unsigned int labelsize=0;
- for(i=0; i<camcount; ++i)
- {
- if(gtk_widget_get_allocated_height(cams[i].label)>labelsize)
- labelsize=gtk_widget_get_allocated_height(cams[i].label);
- }
- // Fit by height
- if(height<data->scaleheight+labelsize)
- {
- data->scaleheight=height-labelsize;
- data->scalewidth=data->scaleheight*4/3;
- }
- if(data->scalewidth<8){data->scalewidth=8;}
- if(data->scaleheight<1){data->scaleheight=1;}
- // TODO: wrapping and stuff
- // Rescale current images to fit
- for(i=0; i<camcount; ++i)
- {
- GdkPixbuf* pixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cams[i].cam));
- if(!pixbuf){continue;}
- pixbuf=gdk_pixbuf_scale_simple(pixbuf, data->scalewidth, data->scaleheight, GDK_INTERP_BILINEAR);
- gtk_image_set_from_pixbuf(GTK_IMAGE(cams[i].cam), pixbuf);
-// TODO: figure out/fix the "static" noise that seems to happen here
- }
-}
-
-void printchat(struct viddata* data, const char* text)
-{
- char bottom=autoscroll_before(data->scroll);
- // Insert new content
- GtkTextIter end;
- gtk_text_buffer_get_end_iter(data->buffer, &end);
- gtk_text_buffer_insert(data->buffer, &end, "\n", -1);
- gtk_text_buffer_insert(data->buffer, &end, text, -1);
- if(bottom){autoscroll_after(data->scroll);}
-}
-
-void printchat_color(struct viddata* data, const char* text, const char* color, unsigned int offset)
-{
- char bottom=autoscroll_before(data->scroll);
- // Insert new content
- GtkTextIter end;
- gtk_text_buffer_get_end_iter(data->buffer, &end);
- gtk_text_buffer_insert(data->buffer, &end, "\n", -1);
- int startnum=gtk_text_iter_get_offset(&end);
- gtk_text_buffer_insert(data->buffer, &end, text, -1);
- // Set color if there was one
- GtkTextIter start;
- gtk_text_buffer_get_iter_at_offset(data->buffer, &start, startnum+offset);
- gtk_text_buffer_apply_tag_by_name(data->buffer, color, &start, &end);
- if(bottom){autoscroll_after(data->scroll);}
-}
-
-char buf[1024];
-gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
-{
- int fd=g_io_channel_unix_get_fd(iochannel);
- struct viddata* data=datap;
- unsigned int i;
- for(i=0; i<1023; ++i)
- {
- if(read(fd, &buf[i], 1)<1){printf("No more data\n"); gtk_main_quit(); return 0;}
- if(buf[i]=='\r'||buf[i]=='\n'){break;}
- }
- buf[i]=0;
- if(!strncmp(buf, "Currently online: ", 18))
- {
- printchat(data, buf);
- char* next=&buf[16];
- while(next)
- {
- char* nick=&next[2];
- next=strstr(nick, ", ");
- if(next){next[0]=0;}
- adduser(nick);
- }
- return 1;
- }
- // Start streams once we're properly connected
- if(!strncmp(buf, "Currently on cam: ", 18))
- {
- printchat(data, buf);
- 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], "/opencam %s\n", user);
- }
- return 1;
- }
- if(!strcmp(buf, "Password required"))
- {
- wait(0); // Reap the previous process
- gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "main")));
- gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(data->gui, "channelpasswordwindow")));
- return 1;
- }
- if(buf[0]=='/') // For the /help text
- {
- printchat(data, buf);
- return 1;
- }
- // Remove escape codes and pick up the text color while we're at it
- char* color=0;
- char* esc;
- char* escend;
- while((esc=strchr(buf, '\x1b'))&&(escend=strchr(esc, 'm')))
- {
- escend[0]=0;
- if(!color && strcmp(&esc[1], "[0")){color=strdup(&esc[1]);}
- memmove(esc, &escend[1], strlen(&escend[1])+1);
- }
- char* space=strchr(buf, ' ');
- // Timestamped events
- 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];
- space=strchr(nick, ' ');
- if(!space){return 1;}
- if(space[-1]==':')
- {
-// TODO: handle /msg (PMs)
- if(config_get_bool("soundradio_cmd") && !fork())
- {
- execlp("sh", "sh", "-c", config_get_str("soundcmd"), (char*)0);
- _exit(0);
- }
- if(!strncmp(space, " /mbs youTube ", 14) && config_get_bool("youtuberadio_cmd") && !fork())
- {
-// TODO: store the PID and make sure it's dead before starting a new video?
-// TODO: only play videos from mods?
- char* id=&space[14];
- char* offset=strchr(id, ' ');
- if(!offset){_exit(1);}
- offset[0]=0;
- offset=&offset[1];
- // Handle format string
- const char* fmt=config_get_str("youtubecmd");
- int len=strlen(fmt)+1;
- len+=strcount(fmt, "%i")*(strlen(id)-2);
- len+=strcount(fmt, "%t")*(strlen(id)-2);
- char cmd[len];
- cmd[0]=0;
- while(fmt[0])
- {
- if(!strncmp(fmt, "%i", 2)){strcat(cmd, id); fmt=&fmt[2]; continue;}
- if(!strncmp(fmt, "%t", 2)){strcat(cmd, offset); fmt=&fmt[2]; continue;}
- for(len=0; fmt[len] && strncmp(&fmt[len], "%i", 2) && strncmp(&fmt[len], "%t", 2); ++len);
- strncat(cmd, fmt, len);
- fmt=&fmt[len];
- }
- execlp("sh", "sh", "-c", cmd, (char*)0);
- _exit(0);
- }
- }
-// TODO: handle logging PMs
- if(config_get_bool("enable_logging")){logger_write(buf, channel, 0);}
- // Insert new content
- printchat_color(data, buf, color, 8);
- if(space[-1]!=':') // Not a message
- {
- if(!strcmp(space, " entered the channel"))
- {
- space[0]=0;
- adduser(nick);
- }
- else if(!strcmp(space, " left the channel"))
- {
- space[0]=0;
- removeuser(nick);
- camera_removebynick(nick);
- }
- else if(!strncmp(space, " changed nickname to ", 21))
- {
- space[0]=0;
- renameuser(nick, &space[21]);
- struct camera* cam=camera_findbynick(nick);
- if(cam)
- {
- free(cam->nick);
- cam->nick=strdup(&space[21]);
- gtk_label_set_text(GTK_LABEL(cam->label), cam->nick);
- }
- }
- }
- free(color);
- return 1;
- }
- if(!strcmp(buf, "Changed color") || !strncmp(buf, "Current color: ", 15))
- {
- printchat_color(data, buf, color, 0);
- free((void*)mycolor);
- mycolor=color;
- return 1;
- }
- if(!strncmp(buf, "Color ", 6))
- {
- printchat_color(data, buf, color, 0);
- }
- free(color);
- if(space && !strcmp(space, " is a moderator."))
- {
- space[0]=0;
- struct user* user=finduser(buf);
- if(user)
- {
- user->ismod=1;
- renameuser(buf, buf); // Update the userlist label
- }
- return 1;
- }
- if(space && !strcmp(space, " is no longer a moderator."))
- {
- space[0]=0;
- struct user* user=finduser(buf);
- if(user){user->ismod=0;}
- return 1;
- }
- // Start a stream when someone cams up
- if(space && !strcmp(space, " cammed up"))
- {
- space[0]=0;
- dprintf(tc_client_in[1], "/opencam %s\n", buf);
- 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_removebynick(nick); // Remove any duplicates
- struct camera* cam=camera_new();
- cam->frame=av_frame_alloc();
- cam->dstframe=av_frame_alloc();
- cam->nick=strdup(nick);
- cam->id=strdup(id);
- cam->vctx=avcodec_alloc_context3(data->vdecoder);
- avcodec_open2(cam->vctx, data->vdecoder, 0);
-#ifdef HAVE_SOUND
- cam->actx=avcodec_alloc_context3(data->adecoder);
- avcodec_open2(cam->actx, data->adecoder, 0);
- cam->samples=0;
-#endif
- cam->cam=gtk_image_new();
- cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_box_set_homogeneous(GTK_BOX(cam->box), 0);
- gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
- cam->label=gtk_label_new(cam->nick);
- gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
- gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
- gtk_widget_show_all(cam->box);
- updatescaling(data, 0, 0);
- while(gtk_events_pending()){gtk_main_iteration();} // Make sure the label gets its size before we calculate scaling
- updatescaling(data, 0, 0);
- return 1;
- }
- if(!strcmp(buf, "Starting outgoing media stream"))
- {
- struct camera* cam=camera_new();
- 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->actx=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);
- cam->label=gtk_label_new(cam->nick);
- gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
- gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
- gtk_widget_show_all(cam->box);
- updatescaling(data, 0, 0);
- while(gtk_events_pending()){gtk_main_iteration();} // Make sure the label gets its size before we calculate scaling
- updatescaling(data, 0, 0);
- return 1;
- }
- if(!strncmp(buf, "VideoEnd: ", 10))
- {
- camera_remove(&buf[10]);
- updatescaling(data, 0, 0);
- 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;
- read(fd, &frameinfo, 1);
- --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)
- {
- pos+=read(fd, pkt.data+pos, size-pos);
- }
-#ifdef HAVE_SOUND
- // 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;}
- int gotframe;
- avcodec_decode_audio4(cam->actx, cam->frame, &gotframe, &pkt);
- if(!gotframe){return 1;}
- #if HAVE_SOUND==avresample
- int outlen=avresample_convert(data->resamplectx, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples);
- #else
- int outlen=swr_convert(data->resamplectx, cam->frame->data, cam->frame->nb_samples, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
- #endif
- camera_playsnd(data->audiopipe, cam, (short*)cam->frame->data[0], outlen);
-#endif
- return 1;
- }
- if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); fflush(stdout); return 1;} // Ignore anything else that isn't video
- 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]);
- 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;
- unsigned char frameinfo;
- read(fd, &frameinfo, 1);
-// printf("Frametype-frame: %x\n", ((unsigned int)frameinfo&0xf0)/16);
-// printf("Frametype-codec: %x\n", (unsigned int)frameinfo&0xf);
- unsigned int pos=0;
- while(pos<size)
- {
- pos+=read(fd, 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]); return 1;}
- pkt.size=size;
- int gotframe;
- avcodec_decode_video2(cam->vctx, cam->frame, &gotframe, &pkt);
- if(!gotframe){return 1;}
-
- // Scale and convert to RGB24 format
- unsigned int bufsize=avpicture_get_size(PIX_FMT_RGB24, data->scalewidth, data->scaleheight);
- unsigned char buf[bufsize];
- cam->dstframe->data[0]=buf;
- cam->dstframe->linesize[0]=data->scalewidth*3;
- struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, data->scalewidth, data->scaleheight, AV_PIX_FMT_RGB24, 0, 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);
-
- GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, data->scalewidth, data->scaleheight, cam->dstframe->linesize[0], 0, 0);
- gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
- // Make sure it gets redrawn in time
- gdk_window_process_updates(gtk_widget_get_window(cam->cam), 1);
-
- return 1;
-}
-
-#ifdef HAVE_SOUND
-void audiothread(int fd)
-{
- ao_initialize();
- ao_sample_format samplefmt;
- samplefmt.bits=16;
- samplefmt.rate=22050;
- samplefmt.channels=1;
- samplefmt.byte_format=AO_FMT_NATIVE; // I'm guessing libavcodec decodes it to native
- samplefmt.matrix=0;
- ao_option clientname={.key="client_name", .value="tc_client/camviewer", .next=0};
- ao_device* dev=ao_open_live(ao_default_driver_id(), &samplefmt, &clientname);
- char buf[2048];
- size_t len;
- while((len=read(fd, buf, 2048))>0)
- {
- ao_play(dev, buf, len);
- }
- ao_close(dev);
-}
-#endif
-
-#ifdef HAVE_V4L2
-pid_t camproc=0;
-void togglecam(GtkCheckMenuItem* item, struct viddata* data)
-{
- if(!gtk_check_menu_item_get_active(item))
- {
- kill(camproc, SIGINT);
- camproc=0;
- dprintf(tc_client_in[1], "/camdown\n");
- dprintf(tc_client[1], "VideoEnd: out\n"); // Close our local display
- return;
- }
- // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
- int campipe[2];
- pipe(campipe);
- dprintf(tc_client_in[1], "/camup\n");
-// printf("Camming up!\n");
- camproc=fork();
- if(!camproc)
- {
- close(campipe[0]);
- 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=0x22; // Note: differentiating between keyframes and non-keyframes seems to break stuff, so let's just go with all being interframes (1=keyframe, 2=interframe, 3=disposable interframe)
- 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(campipe[1], "Video: out %i\n", packet.size+1);
- write(campipe[1], &frameinfo, 1);
- write(campipe[1], packet.data, packet.size);
-
- av_free_packet(&packet);
- }
- sws_freeContext(swsctx);
- _exit(0);
- }
- close(campipe[1]);
- GIOChannel* channel=g_io_channel_unix_new(campipe[0]);
- g_io_channel_set_encoding(channel, 0, 0);
- g_io_add_watch(channel, G_IO_IN, handledata, data);
-}
-#endif
-
-gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
-{
- char bottom=autoscroll_before(data->scroll);
- if(event->width!=gtk_widget_get_allocated_width(data->box))
- {
- updatescaling(data, event->width, 0);
- }
- if(bottom){autoscroll_after(data->scroll);}
- return 0;
-}
-
-void handleresizepane(GObject* obj, GParamSpec* spec, struct viddata* data)
-{
- char bottom=autoscroll_before(data->scroll);
- updatescaling(data, 0, gtk_paned_get_position(GTK_PANED(obj)));
- if(bottom){autoscroll_after(data->scroll);}
-}
-
-gboolean inputkeys(GtkWidget* widget, GdkEventKey* event, void* data)
-{
- if(event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_Down){return 1;}
- if(event->keyval==GDK_KEY_Tab)
- {
- // Tab completion
- int cursor=gtk_editable_get_position(GTK_EDITABLE(widget));;
- GtkEntryBuffer* buf=gtk_entry_get_buffer(GTK_ENTRY(widget));
- const char* text=gtk_entry_buffer_get_text(buf);
- unsigned int namestart=0;
- unsigned int i;
- for(i=0; i<cursor; ++i)
- {
- if(text[i]==' '){namestart=i+1;}
- }
- const char* matches[usercount];
- unsigned int matchcount=0;
- unsigned int commonlen=128;
- for(i=0; i<usercount; ++i)
- {
- if(!strncmp(&text[namestart], userlist[i].nick, cursor-namestart))
- {
- unsigned int j;
- for(j=0; j<matchcount; ++j)
- {
- if(strncmp(matches[j], userlist[i].nick, commonlen))
- {
- for(commonlen=0; userlist[i].nick[commonlen] && matches[j][commonlen] && userlist[i].nick[commonlen]==matches[j][commonlen]; ++commonlen);
- }
- }
- matches[matchcount]=userlist[i].nick;
- ++matchcount;
- }
- }
- if(matchcount==1)
- {
- gtk_entry_buffer_insert_text(buf, cursor, &matches[0][cursor-namestart], -1);
- cursor+=strlen(&matches[0][cursor-namestart]);
- if(!namestart){gtk_entry_buffer_insert_text(buf, cursor, ": ", -1); cursor+=2;}
- gtk_editable_set_position(GTK_EDITABLE(widget), cursor);
- }
- else if(matchcount>1)
- {
- gtk_entry_buffer_insert_text(buf, cursor, &matches[0][cursor-namestart], commonlen+namestart-cursor);
- cursor=namestart+commonlen;
- gtk_editable_set_position(GTK_EDITABLE(widget), cursor);
- }
- return 1;
- }
- return 0;
-}
-
-void sendmessage(GtkEntry* entry, struct viddata* data)
-{
- const char* msg=gtk_entry_get_text(entry);
- dprintf(tc_client_in[1], "%s\n", msg);
- // Don't print commands
- if(!strcmp(msg, "/help") ||
- !strncmp(msg, "/color ", 7) ||
- !strcmp(msg, "/color") ||
- !strcmp(msg, "/colors") ||
- !strncmp(msg, "/nick ", 6) ||
-// !strncmp(msg, "/msg ", 5) || // except PM commands
- !strncmp(msg, "/opencam ", 9) ||
- !strncmp(msg, "/close ", 7) ||
- !strncmp(msg, "/ban ", 5) ||
- !strcmp(msg, "/banlist") ||
- !strncmp(msg, "/forgive ", 9) ||
- !strcmp(msg, "/names") ||
- !strcmp(msg, "/mute") ||
- !strcmp(msg, "/push2talk") ||
- !strcmp(msg, "/camup") ||
- !strcmp(msg, "/camdown") ||
- !strncmp(msg, "/video ", 7) ||
- !strncmp(msg, "/topic ", 7))
- {
- gtk_entry_set_text(entry, "");
- return;
- }
- char text[strlen("[00:00] ")+strlen("You: ")+strlen(msg)+1];
- time_t timestamp=time(0);
- struct tm* t=localtime(×tamp);
- sprintf(text, "[%02i:%02i] ", t->tm_hour, t->tm_min);
- sprintf(&text[8], "You: %s", msg);
- if(config_get_bool("enable_logging")){logger_write(text, channel, 0);}
- printchat_color(data, text, mycolor, 8);
- gtk_entry_set_text(entry, "");
-}
-
-void startsession(GtkButton* button, struct viddata* data)
-{
- gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "startwindow")));
- gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(data->gui, "channelpasswordwindow")));
- gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(data->gui, "main")));
- const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_nick")));
- channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_channel")));
- const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "channelpassword")));
- const char* acc_user=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_username")));
- const char* acc_pass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_password")));
- 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);
- dup2(tc_client_in[0], 0);
- if(acc_user[0])
- {
- execl("./tc_client", "./tc_client", "-u", acc_user, channel, nick, chanpass, (char*)0);
- }else{
- execl("./tc_client", "./tc_client", channel, nick, chanpass, (char*)0);
- }
- }
- if(acc_user[0]){dprintf(tc_client_in[1], "%s\n", acc_pass);}
- write(tc_client_in[1], "/color\n", 7);
- GIOChannel* tcchannel=g_io_channel_unix_new(tc_client[0]);
- g_io_channel_set_encoding(tcchannel, 0, 0);
- g_io_add_watch(tcchannel, G_IO_IN, handledata, data);
- // Remember, if asked to
- char save=0;
- if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(data->gui, "start_rememberchan"))))
- {
- config_set("remember_nick", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_nick"))));
- config_set("remember_chan", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "start_channel"))));
- config_set("remember_chan_nick", "True");
- save=1;
- }
- else if(config_get_bool("remember_chan_nick")) // Remove previously remembered info
- {
- config_set("remember_nick", "");
- config_set("remember_chan", "");
- config_set("remember_chan_nick", "False");
- save=1;
- }
- // Same for account
- if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(data->gui, "start_rememberacc"))))
- {
- config_set("remember_username", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_username"))));
- config_set("remember_password", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(data->gui, "acc_password"))));
- config_set("remember_acc", "True");
- save=1;
- }
- else if(config_get_bool("remember_acc")) // Remove previously remembered info
- {
- config_set("remember_username", "");
- config_set("remember_password", "");
- config_set("remember_acc", "False");
- save=1;
- }
- if(save){config_save();}
-}
-
-int main(int argc, char** argv)
-{
- struct viddata data={0,0,0,0,0};
- avcodec_register_all();
- data.vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
- data.adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
-
-#ifdef HAVE_SOUND
- #if HAVE_SOUND==avresample
- data.resamplectx=avresample_alloc_context();
- av_opt_set_int(data.resamplectx, "in_channel_layout", AV_CH_FRONT_CENTER, 0);
- av_opt_set_int(data.resamplectx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
- // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
- av_opt_set_int(data.resamplectx, "in_sample_rate", 11025, 0);
- av_opt_set_int(data.resamplectx, "out_channel_layout", AV_CH_FRONT_CENTER, 0);
- av_opt_set_int(data.resamplectx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
- av_opt_set_int(data.resamplectx, "out_sample_rate", 22050, 0);
- avresample_open(data.resamplectx);
- #else
- data.resamplectx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0);
- swr_init(data.swrctx);
- #endif
- int audiopipe[2];
- pipe(audiopipe);
- data.audiopipe=audiopipe[1];
- if(!fork())
- {
- prctl(PR_SET_PDEATHSIG, SIGHUP);
- close(audiopipe[1]);
- audiothread(audiopipe[0]);
- _exit(0);
- }
- close(audiopipe[0]);
-#endif
-
- gtk_init(&argc, &argv);
- GtkBuilder* gui=gtk_builder_new_from_file("gtkgui.glade");
- gtk_builder_connect_signals(gui, 0);
- data.gui=gui;
-
-#ifdef HAVE_V4L2
- GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_camera"));
- g_signal_connect(item, "toggled", G_CALLBACK(togglecam), &data);
- data.vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
-#else
- GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast"));
- gtk_widget_destroy(item);
-#endif
-
- item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_options_settings"));
- g_signal_connect(item, "activate", G_CALLBACK(showsettings), gui);
-
- data.box=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
- userlistwidget=GTK_WIDGET(gtk_builder_get_object(gui, "userlistbox"));
- GtkWidget* chatview=GTK_WIDGET(gtk_builder_get_object(gui, "chatview"));
- data.scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gtk_builder_get_object(gui, "chatscroll")));
-
- data.buffer=gtk_text_view_get_buffer(GTK_TEXT_VIEW(chatview));
- #define colormap(code, color) gtk_text_buffer_create_tag(data.buffer, code, "foreground", color, (char*)0)
- colormap("[31", "#821615");
- colormap("[31;1", "#c53332");
- colormap("[33", "#a08f23");
- //colormap("[33", "#a78901");
- colormap("[33;1", "#919104");
- colormap("[32;1", "#7bb224");
- //colormap("[32;1", "#7db257");
- colormap("[32", "#487d21");
- colormap("[36", "#00a990");
- colormap("[34;1", "#32a5d9");
- //colormap("[34;1", "#1d82eb");
- colormap("[34", "#1965b6");
- colormap("[35", "#5c1a7a");
- colormap("[35;1", "#9d5bb5");
- //colormap("[35;1", "#c356a3");
- //colormap("[35;1", "#b9807f");
-
- GtkWidget* panes=GTK_WIDGET(gtk_builder_get_object(gui, "vpaned"));
- g_signal_connect(panes, "notify::position", G_CALLBACK(handleresizepane), &data);
-
- GtkWidget* inputfield=GTK_WIDGET(gtk_builder_get_object(gui, "inputfield"));
- g_signal_connect(inputfield, "activate", G_CALLBACK(sendmessage), &data);
- g_signal_connect(inputfield, "key-press-event", G_CALLBACK(inputkeys), &data);
-
- config_load();
- // Sound
- GtkWidget* option=GTK_WIDGET(gtk_builder_get_object(gui, "soundradio_cmd"));
- g_signal_connect(option, "toggled", G_CALLBACK(toggle_soundcmd), gui);
- // Logging
- option=GTK_WIDGET(gtk_builder_get_object(gui, "enable_logging"));
- g_signal_connect(option, "toggled", G_CALLBACK(toggle_logging), gui);
- option=GTK_WIDGET(gtk_builder_get_object(gui, "save_settings"));
- g_signal_connect(option, "clicked", G_CALLBACK(savesettings), gui);
- // Youtube
- option=GTK_WIDGET(gtk_builder_get_object(gui, "youtuberadio_cmd"));
- g_signal_connect(option, "toggled", G_CALLBACK(toggle_youtubecmd), gui);
-
- GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "main"));
- g_signal_connect(window, "configure-event", G_CALLBACK(handleresize), &data);
-
- // Start window and channel password window signals
- GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "connectbutton"));
- g_signal_connect(button, "clicked", G_CALLBACK(startsession), &data);
- button=GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordbutton"));
- g_signal_connect(button, "clicked", G_CALLBACK(startsession), &data);
- button=GTK_WIDGET(gtk_builder_get_object(gui, "channelpassword"));
- g_signal_connect(button, "activate", G_CALLBACK(startsession), &data);
- GtkWidget* startwindow=GTK_WIDGET(gtk_builder_get_object(gui, "startwindow"));
- // Set channel and nick from last session
- if(config_get_bool("remember_chan_nick"))
- {
- gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "start_nick")), config_get_str("remember_nick"));
- gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "start_channel")), config_get_str("remember_chan"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "start_rememberchan")), 1);
- }
- // Set username and password from last session
- if(config_get_bool("remember_acc"))
- {
- gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")), config_get_str("remember_username"));
- gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")), config_get_str("remember_password"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "start_rememberacc")), 1);
- }
- gtk_widget_show_all(startwindow);
-
- gtk_main();
-
- camera_cleanup();
-#ifdef HAVE_SOUND
- #if HAVE_SOUND==avresample
- avresample_free(&data.resamplectx);
- #else
- swr_free(&data.swrctx);
- #endif
-#endif
- return 0;
-}
diff --git a/utilities/gtk/compat.c b/utilities/gtk/compat.c
deleted file mode 100644
index 3f2efd4..0000000
--- a/utilities/gtk/compat.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <ctype.h>
-#include <sys/stat.h>
-#include <gtk/gtk.h>
-#include "compat.h"
-
-#if GTK_MAJOR_VERSION<3
- GtkWidget* gtk_box_new(int vertical, int spacing)
- {
- if(vertical)
- {
- return gtk_vbox_new(1, spacing);
- }else{
- return gtk_hbox_new(1, spacing);
- }
- }
- int gtk_widget_get_allocated_width(GtkWidget* widget)
- {
- GtkAllocation alloc;
- gtk_widget_get_allocation(widget, &alloc);
- return alloc.width;
- }
- int gtk_widget_get_allocated_height(GtkWidget* widget)
- {
- GtkAllocation alloc;
- gtk_widget_get_allocation(widget, &alloc);
- return alloc.height;
- }
- char* newline(char* line)
- {
- unsigned int i;
- for(i=0; line[i] && line[i]!='\n' && line[i]!='\r'; ++i);
- return &line[i];
- }
- // Hack to let us load a glade GUI designed for gtk+-3.x
- GtkBuilder* gtk_builder_new_from_file(const char* filename)
- {
- struct stat st;
- if(stat(filename, &st)){return 0;}
- char buf[st.st_size+10];
- int f=open(filename, O_RDONLY);
- read(f, buf, st.st_size);
- close(f);
- buf[st.st_size]=0;
- char* pos;
- if((pos=strstr(buf, "<requires "))) // Don't require anything
- {
- char* end=newline(pos);
- memmove(pos, end, strlen(end)+1);
- }
- // Convert orientation properties into GtkV/GtkH object types
- char* orientation;
- while((orientation=strstr(buf, "<property name=\"orientation\">")))
- {
- char dir=toupper(orientation[29]);
- pos=newline(orientation);
- memmove(orientation, pos, strlen(pos)+1);
- pos=orientation;
- while(pos>buf && strncmp(pos, "class=\"Gtk", 10)){--pos;}
- if(pos>buf)
- {
- memmove(&pos[11], &pos[10], strlen(&pos[10])+1);
- pos[10]=dir;
- }
- }
- // Convert remaining GtkBoxes and GtkPaneds with the default orientation
- while((pos=strstr(buf, "class=\"GtkBox\"")) || (pos=strstr(buf, "class=\"GtkPaned\"")))
- {
- memmove(&pos[11], &pos[10], strlen(&pos[10])+1);
- pos[10]='H'; // Default is horizontal
- }
- GtkBuilder* gui=gtk_builder_new();
- GError* error=0;
- if(!gtk_builder_add_from_string(gui, buf, -1, &error)){g_error("%s\n", error->message);}
- return gui;
- }
-#endif
diff --git a/utilities/gtk/compat.h b/utilities/gtk/compat.h
deleted file mode 100644
index 88004d2..0000000
--- a/utilities/gtk/compat.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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/>.
-*/
-#if GTK_MAJOR_VERSION<3
- #define GTK_ORIENTATION_HORIZONTAL 0
- #define GTK_ORIENTATION_VERTICAL 1
- extern GtkWidget* gtk_box_new(int vertical, int spacing);
- extern int gtk_widget_get_allocated_width(GtkWidget* widget);
- extern int gtk_widget_get_allocated_height(GtkWidget* widget);
- extern GtkBuilder* gtk_builder_new_from_file(const char* filename);
-#endif
diff --git a/utilities/gtk/config.c b/utilities/gtk/config.c
deleted file mode 100644
index 7d5a813..0000000
--- a/utilities/gtk/config.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include "config.h"
-
-struct configitem
-{
- const char* name;
- char* value;
-};
-
-struct configitem* configitems=0;
-unsigned int configitemcount=0;
-
-void config_load(void)
-{
- const char* home=getenv("HOME");
- char filename[strlen(home)+strlen("/.config/tc_client-gtk0")];
- sprintf(filename, "%s/.config/tc_client-gtk", home);
- FILE* f=fopen(filename, "r");
- if(!f){return;}
- char buf[2048];
- while(fgets(buf, 2048, f))
- {
- char* sep=strchr(buf, ':');
- if(!sep){continue;}
- sep[0]=0;
- char* value=&sep[1];
- while(value[0]==' '){value=&value[1];}
- while((sep=strchr(value, '\r'))||(sep=strchr(value, '\n'))){sep[0]=0;}
- ++configitemcount;
- configitems=realloc(configitems, sizeof(struct configitem)*configitemcount);
- configitems[configitemcount-1].name=strdup(buf);
- configitems[configitemcount-1].value=strdup(value);
- }
- fclose(f);
-}
-
-void config_save(void)
-{
- const char* home=getenv("HOME");
- char filename[strlen(home)+strlen("/.config/tc_client-gtk0")];
- sprintf(filename, "%s/.config", home);
- mkdir(filename, 0700);
- strcat(filename, "/tc_client-gtk");
- FILE* f=fopen(filename, "w");
- if(!f){perror("fopen(~/.config/tc_client-gtk)"); return;}
- unsigned int i;
- for(i=0; i<configitemcount; ++i)
- {
- fprintf(f, "%s: %s\n", configitems[i].name, configitems[i].value);
- }
- fclose(f);
-}
-
-char config_get_bool(const char* name)
-{
- unsigned int i;
- for(i=0; i<configitemcount; ++i)
- {
- if(!strcmp(configitems[i].name, name)){return !strcasecmp(configitems[i].value, "True");}
- }
- return 0;
-}
-
-const char* config_get_str(const char* name)
-{
- unsigned int i;
- for(i=0; i<configitemcount; ++i)
- {
- if(!strcmp(configitems[i].name, name)){return configitems[i].value;}
- }
- return "";
-}
-
-int config_get_int(const char* name)
-{
- unsigned int i;
- for(i=0; i<configitemcount; ++i)
- {
- if(!strcmp(configitems[i].name, name)){return atoi(configitems[i].value);}
- }
- return 0;
-}
-
-void config_set(const char* name, const char* value)
-{
- unsigned int i;
- for(i=0; i<configitemcount; ++i)
- {
- if(!strcmp(configitems[i].name, name))
- {
- free(configitems[i].value);
- configitems[i].value=strdup(value);
- return;
- }
- }
- ++configitemcount;
- configitems=realloc(configitems, sizeof(struct configitem)*configitemcount);
- configitems[configitemcount-1].name=strdup(name);
- configitems[configitemcount-1].value=strdup(value);
-}
-
-void config_set_int(const char* name, int value)
-{
- int size=snprintf(0,0, "%i", value);
- char buf[size+1];
- sprintf(buf, "%i", value);
- config_set(name, buf);
-}
diff --git a/utilities/gtk/config.h b/utilities/gtk/config.h
deleted file mode 100644
index a12032c..0000000
--- a/utilities/gtk/config.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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/>.
-*/
-extern void config_load(void);
-extern void config_save(void);
-
-extern char config_get_bool(const char* name);
-extern const char* config_get_str(const char* name);
-extern int config_get_int(const char* name);
-
-extern void config_set(const char* name, const char* value);
-extern void config_set_int(const char* name, int value);
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
deleted file mode 100644
index c59a207..0000000
--- a/utilities/gtk/gui.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <gtk/gtk.h>
-#include "gui.h"
-#include "config.h"
-#include "logging.h"
-
-char autoscroll_before(GtkAdjustment* scroll)
-{
- // Figure out if we're at the bottom and should autoscroll with new content
- int upper=gtk_adjustment_get_upper(scroll);
- int size=gtk_adjustment_get_page_size(scroll);
- int value=gtk_adjustment_get_value(scroll);
- return (value+size==upper);
-}
-
-void autoscroll_after(GtkAdjustment* scroll)
-{
- while(gtk_events_pending()){gtk_main_iteration();} // Make sure the textview's new size affects scroll's "upper" value first
- int upper=gtk_adjustment_get_upper(scroll);
- int size=gtk_adjustment_get_page_size(scroll);
- gtk_adjustment_set_value(scroll, upper-size);
-}
-
-void settings_reset(GtkBuilder* gui)
-{
- // Sound
- GtkWidget* option=GTK_WIDGET(gtk_builder_get_object(gui, "soundradio_cmd"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option), config_get_bool("soundradio_cmd"));
- option=GTK_WIDGET(gtk_builder_get_object(gui, "soundcmd"));
- gtk_entry_set_text(GTK_ENTRY(option), config_get_str("soundcmd"));
- // Logging
- option=GTK_WIDGET(gtk_builder_get_object(gui, "enable_logging"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option), config_get_bool("enable_logging"));
- GtkWidget* logpath=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_channel"));
- gtk_entry_set_text(GTK_ENTRY(logpath), config_get_str("logpath_channel"));
- logpath=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_pm"));
- gtk_entry_set_text(GTK_ENTRY(logpath), config_get_str("logpath_pm"));
- // Youtube
- option=GTK_WIDGET(gtk_builder_get_object(gui, "youtuberadio_cmd"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option), config_get_bool("youtuberadio_cmd"));
- option=GTK_WIDGET(gtk_builder_get_object(gui, "youtubecmd"));
- gtk_entry_set_text(GTK_ENTRY(option), config_get_str("youtubecmd"));
-}
-
-void showsettings(GtkMenuItem* item, GtkBuilder* gui)
-{
- settings_reset(gui);
- GtkWidget* w=GTK_WIDGET(gtk_builder_get_object(gui, "settings"));
- gtk_widget_show_all(w);
-}
-
-void savesettings(GtkButton* button, GtkBuilder* gui)
-{
- // Sound
- GtkWidget* soundcmd=GTK_WIDGET(gtk_builder_get_object(gui, "soundcmd"));
- config_set("soundcmd", gtk_entry_get_text(GTK_ENTRY(soundcmd)));
- GtkWidget* soundradio_cmd=GTK_WIDGET(gtk_builder_get_object(gui, "soundradio_cmd"));
- config_set("soundradio_cmd", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(soundradio_cmd))?"True":"False");
- // Logging
- GtkWidget* logging=GTK_WIDGET(gtk_builder_get_object(gui, "enable_logging"));
- config_set("enable_logging", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(logging))?"True":"False");
- if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(logging))){logger_close_all();}
- GtkWidget* logpath=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_channel"));
- config_set("logpath_channel", gtk_entry_get_text(GTK_ENTRY(logpath)));
- logpath=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_pm"));
- config_set("logpath_pm", gtk_entry_get_text(GTK_ENTRY(logpath)));
- // Youtube
- GtkWidget* youtubecmd=GTK_WIDGET(gtk_builder_get_object(gui, "youtubecmd"));
- config_set("youtubecmd", gtk_entry_get_text(GTK_ENTRY(youtubecmd)));
- GtkWidget* youtuberadio_cmd=GTK_WIDGET(gtk_builder_get_object(gui, "youtuberadio_cmd"));
- config_set("youtuberadio_cmd", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(youtuberadio_cmd))?"True":"False");
-
- config_save();
- GtkWidget* settings=GTK_WIDGET(gtk_builder_get_object(gui, "settings"));
- gtk_widget_hide(settings);
-}
-
-void toggle_soundcmd(GtkToggleButton* button, GtkBuilder* gui)
-{
- GtkWidget* field=GTK_WIDGET(gtk_builder_get_object(gui, "soundcmd"));
- gtk_widget_set_sensitive(field, gtk_toggle_button_get_active(button));
-}
-
-void toggle_logging(GtkToggleButton* button, GtkBuilder* gui)
-{
- GtkWidget* field1=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_channel"));
- GtkWidget* field2=GTK_WIDGET(gtk_builder_get_object(gui, "logpath_pm"));
- gtk_widget_set_sensitive(field1, gtk_toggle_button_get_active(button));
- gtk_widget_set_sensitive(field2, gtk_toggle_button_get_active(button));
-}
-
-void toggle_youtubecmd(GtkToggleButton* button, GtkBuilder* gui)
-{
- GtkWidget* field=GTK_WIDGET(gtk_builder_get_object(gui, "youtubecmd"));
- gtk_widget_set_sensitive(field, gtk_toggle_button_get_active(button));
-}
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
deleted file mode 100644
index e4cfa68..0000000
--- a/utilities/gtk/gui.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <gtk/gtk.h>
-
-extern char autoscroll_before(GtkAdjustment* scroll);
-extern void autoscroll_after(GtkAdjustment* scroll);
-extern void settings_reset(GtkBuilder* gui);
-extern void showsettings(GtkMenuItem* item, GtkBuilder* gui);
-extern void savesettings(GtkButton* button, GtkBuilder* gui);
-extern void toggle_soundcmd(GtkToggleButton* button, GtkBuilder* gui);
-extern void toggle_logging(GtkToggleButton* button, GtkBuilder* gui);
-extern void toggle_youtubecmd(GtkToggleButton* button, GtkBuilder* gui);
diff --git a/utilities/gtk/logging.c b/utilities/gtk/logging.c
deleted file mode 100644
index 6b1a107..0000000
--- a/utilities/gtk/logging.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <time.h>
-#include <sys/stat.h>
-#include "config.h"
-#include "../stringutils.h"
-
-struct logfile
-{
- FILE* f;
- const char* path;
-};
-struct logfile* logfiles=0;
-unsigned int logfilecount=0;
-
-void logger_write(const char* line, const char* channel, const char* nick)
-{
- const char* home=getenv("HOME");
- const char* path=config_get_str(nick?"logpath_pm":"logpath_channel");
- int namelen=strlen(path)+1;
- namelen+=strcount(path, "%h")*(strlen(home)-2);
- namelen+=strcount(path, "%c")*(strlen(channel)-2);
- if(nick){namelen+=strcount(path, "%n")*(strlen(nick)-2);}
- char filename[namelen];
- filename[0]=0;
- int len;
- while(path[0])
- {
- if(!strncmp(path, "%h", 2)){strcat(filename, home); path=&path[2]; continue;}
- if(!strncmp(path, "%c", 2)){strcat(filename, channel); path=&path[2]; continue;}
- if(nick && !strncmp(path, "%n", 2)){strcat(filename, nick); path=&path[2]; continue;}
- for(len=0; path[len] && strncmp(&path[len], "%h", 2) && strncmp(&path[len], "%c", 2) && (!nick||strncmp(&path[len], "%n", 2)); ++len);
- strncat(filename, path, len);
- path=&path[len];
- }
- unsigned int i;
- for(i=0; i<logfilecount; ++i)
- {
- if(!strcmp(filename, logfiles[i].path)){break;}
- }
- if(i==logfilecount)
- {
- ++logfilecount;
- logfiles=realloc(logfiles, logfilecount*sizeof(struct logfile));
- logfiles[i].path=strdup(filename);
- // Make sure the whole path exists
- char* sep=filename;
- while((sep=strchr(sep, '/')))
- {
- sep[0]=0;
-printf("Creating '%s' if it doesn't exist yet\n", filename);
- mkdir(filename, 0700);
- sep[0]='/';
- sep=&sep[1];
- }
- logfiles[i].f=fopen(logfiles[i].path, "a");
- if(!logfiles[i].f)
- {
- perror("fopen(logfile)");
- free((void*)logfiles[i].path);
- --logfilecount;
- return;
- }
- fprintf(logfiles[i].f, "Opening logfile on %ti (TODO: format)\n", time(0));
- }
- fprintf(logfiles[i].f, "%s\n", line);
-}
-
-void logger_close_all(void)
-{
- unsigned int i;
- for(i=0; i<logfilecount; ++i)
- {
- fclose(logfiles[i].f);
- free((void*)logfiles[i].path);
- }
- free(logfiles);
- logfiles=0;
- logfilecount=0;
-}
diff --git a/utilities/gtk/logging.h b/utilities/gtk/logging.h
deleted file mode 100644
index 76c1831..0000000
--- a/utilities/gtk/logging.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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/>.
-*/
-
-extern void logger_write(const char* line, const char* channel, const char* nick);
-extern void logger_close_all(void);
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
deleted file mode 100644
index e7fbb24..0000000
--- a/utilities/gtk/media.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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 <stdlib.h>
-#include <string.h>
-#include <gtk/gtk.h>
-#include <libavcodec/avcodec.h>
-#include "media.h"
-struct camera* cams=0;
-unsigned int camcount=0;
-
-#ifdef HAVE_SOUND
-// Experimental mixer, not sure if it really works
-void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount)
-{
- if(cam->samples)
- {
-// int sources=1;
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- if(!cams[i].samples){continue;}
- if(cam==&cams[i]){continue;}
- unsigned j;
- for(j=0; j<cam->samplecount && j<cams[i].samplecount; ++j)
- {
- cam->samples[j]+=cams[i].samples[j];
- }
- free(cams[i].samples);
- cams[i].samples=0;
-// ++sources;
- }
- write(audiopipe, cam->samples, cam->samplecount*sizeof(short));
- free(cam->samples);
-// printf("Mixed sound from %i sources (cam: %p)\n", sources, cam);
- }
- cam->samples=malloc(samplecount*sizeof(short));
- memcpy(cam->samples, samples, samplecount*sizeof(short));
- cam->samplecount=samplecount;
-}
-#endif
-
-void camera_remove(const char* id)
-{
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- if(!strcmp(cams[i].id, id))
- {
- gtk_widget_destroy(cams[i].box);
- av_frame_free(&cams[i].frame);
- avcodec_free_context(&cams[i].vctx);
-#ifdef HAVE_SOUND
- avcodec_free_context(&cams[i].actx);
-#endif
- free(cams[i].id);
- free(cams[i].nick);
- --camcount;
- memmove(&cams[i], &cams[i+1], (camcount-i)*sizeof(struct camera));
- break;
- }
- }
-}
-
-void camera_removebynick(const char* nick)
-{
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- if(!strcmp(cams[i].nick, nick))
- {
- gtk_widget_destroy(cams[i].box);
- av_frame_free(&cams[i].frame);
- avcodec_free_context(&cams[i].vctx);
-#ifdef HAVE_SOUND
- avcodec_free_context(&cams[i].actx);
-#endif
- free(cams[i].id);
- free(cams[i].nick);
- --camcount;
- memmove(&cams[i], &cams[i+1], (camcount-i)*sizeof(struct camera));
- break;
- }
- }
-}
-
-struct camera* camera_find(const char* id)
-{
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- if(!strcmp(cams[i].id, id)){return &cams[i];}
- }
- return 0;
-}
-
-struct camera* camera_findbynick(const char* nick)
-{
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- if(!strcmp(cams[i].nick, nick)){return &cams[i];}
- }
- return 0;
-}
-
-struct camera* camera_new(void)
-{
- ++camcount;
- cams=realloc(cams, sizeof(struct camera)*camcount);
- return &cams[camcount-1];
-}
-
-void camera_cleanup(void)
-{
- unsigned int i;
- for(i=0; i<camcount; ++i)
- {
- av_frame_free(&cams[i].frame);
- avcodec_free_context(&cams[i].vctx);
-#ifdef HAVE_SOUND
- avcodec_free_context(&cams[i].actx);
-#endif
- free(cams[i].id);
- free(cams[i].nick);
- }
- free(cams);
-}
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
deleted file mode 100644
index 159e998..0000000
--- a/utilities/gtk/media.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- tc_client-gtk, a graphical user interface for tc_client
- 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/>.
-*/
-struct camera
-{
- AVFrame* frame;
- AVFrame* dstframe;
- GtkWidget* cam;
- AVCodecContext* vctx;
- AVCodecContext* actx;
- short* samples;
- unsigned int samplecount;
- char* id;
- char* nick;
- GtkWidget* box; // holds label and cam
- GtkWidget* label;
-};
-extern struct camera* cams;
-extern unsigned int camcount;
-
-#ifdef HAVE_SOUND
-extern void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount);
-#endif
-extern void camera_remove(const char* nick);
-extern void camera_removebynick(const char* nick);
-extern struct camera* camera_find(const char* id);
-extern struct camera* camera_findbynick(const char* nick);
-extern struct camera* camera_new(void);
-extern void camera_cleanup(void);
diff --git a/utilities/gtk/userlist.c b/utilities/gtk/userlist.c
deleted file mode 100644
index 89d1df5..0000000
--- a/utilities/gtk/userlist.c
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- camviewer, a sample application to view tinychat cam streams
- 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 <stdlib.h>
-#include <string.h>
-#include <gtk/gtk.h>
-#include "userlist.h"
-
-struct user* userlist=0;
-unsigned int usercount=0;
-GtkWidget* userlistwidget=0;
-
-struct user* finduser(const char* nick)
-{
- unsigned int i;
- for(i=0; i<usercount; ++i)
- {
- if(!strcmp(userlist[i].nick, nick)){return &userlist[i];}
- }
- return 0;
-}
-
-struct user* adduser(const char* nick)
-{
- struct user* user=finduser(nick);
- if(user){return user;} // User already existed (this might happen when running /names)
- ++usercount;
- userlist=realloc(userlist, sizeof(struct user)*usercount);
- userlist[usercount-1].nick=strdup(nick);
- userlist[usercount-1].label=gtk_label_new(nick); // TODO: some kind of menubutton for actions?
-#if GTK_MAJOR_VERSION>=3
- gtk_widget_set_halign(userlist[usercount-1].label, GTK_ALIGN_START);
-#endif
- userlist[usercount-1].ismod=0;
- gtk_box_pack_start(GTK_BOX(userlistwidget), userlist[usercount-1].label, 0, 0, 0);
- gtk_widget_show(userlist[usercount-1].label);
- return &userlist[usercount-1];
-}
-
-void renameuser(const char* old, const char* newnick)
-{
- struct user* user=finduser(old);
- if(!user){return;}
- free(user->nick);
- user->nick=strdup(newnick);
- if(user->ismod)
- {
- char newlabel[strlen(newnick)+2];
- newlabel[0]='@';
- strcpy(&newlabel[1], newnick);
- gtk_label_set_text(GTK_LABEL(user->label), newlabel);
- }else{
- gtk_label_set_text(GTK_LABEL(user->label), newnick);
- }
-}
-
-void removeuser(const char* nick)
-{
- unsigned int i;
- for(i=0; i<usercount; ++i)
- {
- if(!strcmp(userlist[i].nick, nick))
- {
- free(userlist[i].nick);
- gtk_widget_destroy(userlist[i].label);
- --usercount;
- memmove(&userlist[i], &userlist[i+1], (usercount-i)*sizeof(struct user));
- return;
- }
- }
-}
diff --git a/utilities/gtk/userlist.h b/utilities/gtk/userlist.h
deleted file mode 100644
index 98b886e..0000000
--- a/utilities/gtk/userlist.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- camviewer, a sample application to view tinychat cam streams
- 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/>.
-*/
-struct user
-{
- char* nick;
- GtkWidget* label;
-// unsigned int id; // hm, tc_client doesn't share IDs other than in guestnicks, this might be useful for a ban-after-they-left situation
- char ismod;
-};
-
-extern struct user* userlist;
-extern unsigned int usercount;
-extern GtkWidget* userlistwidget;
-
-extern struct user* finduser(const char* nick);
-extern struct user* adduser(const char* nick);
-extern void renameuser(const char* old, const char* newnick);
-extern void removeuser(const char* nick);
diff --git a/utilities/irchack/irchack.c b/utilities/irchack/irchack.c
index d9fc248..47289c3 100644
--- a/utilities/irchack/irchack.c
+++ b/utilities/irchack/irchack.c
@@ -24,7 +24,25 @@
#include <sys/socket.h>
#include <ctype.h>
#include <signal.h>
-#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
// ANSI colors and their IRC equivalents
struct color{const char* ansi; const char* irc;};
diff --git a/utilities/modbot/commands.html b/utilities/modbot/commands.html
deleted file mode 100644
index b7b8325..0000000
--- a/utilities/modbot/commands.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<html>
-<head>
- <title>modbot commands</title>
- <style>
- <!--
- th{text-align:left;}
- -->
- </style>
-</head>
-<body>
- modbot is a utility to queue videos to be played, with a list of approved videos that will automatically play when requested. Below is a list of supported commands.<br /><br />
- <table cellspacing="1" border="0">
- <tr><th>Command</th><th>Description</th></tr>
- <tr><td>!request <link/searchterm></td><td>request a video to be played</td></tr>
- <tr><td>!queue</td><td>get the number of songs in queue and which (if any) need to be approved</td></tr>
- <tr><td>!wrongrequest</td><td>undo the last request you made</td></tr>
- <tr><td>!requestedby</td><td>see who requested the current video</td></tr>
- <tr><td>!modstats</td><td>get a percentage of how often there are mods in the channel (aside from modbot)</td></tr>
- <tr><td>!syncvid</td><td>synchronize video position (or see a video that was started while having youtube videos disabled in the flash client)</td></tr>
- <tr><th colspan="2">Mod commands:</th></tr>
- <tr><td>!playnext</td><td>play the next video in queue without approving it (to see if it's ok)</td></tr>
- <tr><td>!approve</td><td>mark the currently playing video as good, or if none is playing the next in queue</td></tr>
- <tr><td>!approve <link/searchterm></td><td>mark the specified video as okay</td></tr>
- <tr><td>!approve next</td><td>mark the next not yet approved video as okay</td></tr>
- <tr><td>!approve entire queue</td><td>approve all videos in queue (for playlists)</td></tr>
- <tr><td>!badvid</td><td>stop playing the current video and mark it as bad</td></tr>
- <tr><td>!badvid <link/searchterm></td><td>mark the specified video as bad, preventing it from ever being queued again</td></tr>
- </table><br />
- You can also just play videos manually/the good old way and they will be marked as good.
-</body>
-</html>
diff --git a/utilities/list.c b/utilities/modbot/list.c
similarity index 98%
rename from utilities/list.c
rename to utilities/modbot/list.c
index 707a182..bae53df 100644
--- a/utilities/list.c
+++ b/utilities/modbot/list.c
@@ -1,5 +1,5 @@
/*
- A simple list implementation
+ modbot, a bot for tc_client that queues and plays videos
Copyright (C) 2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
diff --git a/utilities/list.h b/utilities/modbot/list.h
similarity index 94%
rename from utilities/list.h
rename to utilities/modbot/list.h
index 5e8e7d5..1462584 100644
--- a/utilities/list.h
+++ b/utilities/modbot/list.h
@@ -1,5 +1,5 @@
/*
- A simple list implementation
+ modbot, a bot for tc_client that queues and plays videos
Copyright (C) 2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index bb193b8..87155b9 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};
@@ -34,24 +34,9 @@ struct queue queue={0,0};
struct list goodvids={0,0}; // pre-approved videos
struct list badvids={0,0}; // not allowed, essentially banned
char* playing=0;
-char* requester=0;
time_t started=0;
int tc_client;
-time_t time_with_mods=0;
-time_t time_modcount;
-char havemods=0;
-void timemods(void)
-{
- time_t now=time(0);
- if(havemods)
- {
- time_with_mods+=now-time_modcount;
- }
- time_modcount=now;
- havemods=(mods.itemcount>1); // Not counting modbot as a mod
-}
-
void say(const char* pm, const char* fmt, ...)
{
va_list va;
@@ -71,51 +56,32 @@ void say(const char* pm, const char* fmt, ...)
write(tc_client, buf, strlen(buf));
}
-void getvidinfo(const char* vid, const char* type, char* buf, char* errbuf, unsigned int len)
+void getvidinfo(const char* vid, const char* type, char* buf, unsigned int len)
{
int out[2];
- int err[2];
pipe(out);
- pipe(err);
if(!fork())
{
close(out[0]);
- close(err[0]);
dup2(out[1], 1);
- dup2(err[1], 2);
execlp("youtube-dl", "youtube-dl", "--default-search", "auto", type, "--", vid, (char*)0);
perror("execlp(youtube-dl)");
_exit(1);
}
wait(0);
close(out[1]);
- close(err[1]);
- size_t r;
- // Read output
- r=read(out[0], buf, len-1);
- if(r<0){r=0;}
- while(r>0 && (buf[r-1]=='\r' || buf[r-1]=='\n')){--r;} // Strip newlines
- buf[r]=0;
+ len=read(out[0], buf, len-1);
+ if(len<0){len=0;}
+ while(len>0 && (buf[len-1]=='\r' || buf[len-1]=='\n')){--len;} // Strip newlines
+ buf[len]=0;
close(out[0]);
- // Read any error messages
- if(errbuf)
- {
- r=read(err[0], errbuf, len-1);
- if(r<0){r=0;}
- while(r>0 && (errbuf[r-1]=='\r' || errbuf[r-1]=='\n')){--r;} // Strip newlines
- errbuf[r]=0;
- char* newline; // No need for newlines in error messages
- while((newline=strchr(errbuf, '\n'))){newline[0]=' ';}
- while((newline=strchr(errbuf, '\r'))){newline[0]=' ';}
- }
- close(err[0]);
}
unsigned int getduration(const char* vid)
{
char timebuf[128];
timebuf[0]=':'; // Sacrifice 1 byte to avoid having to deal with a special case later on, where no ':' is found and we go from the start of the string, but only once
- getvidinfo(vid, "--get-duration", &timebuf[1], 0, 127);
+ getvidinfo(vid, "--get-duration", &timebuf[1], 127);
if(!timebuf[1]){printf("Failed to get video duration using youtube-dl, assuming 60s\n"); return 60;} // If using youtube-dl fails, assume videos are 1 minute long
// youtube-dl prints it out in hh:mm:ss format, convert it to plain seconds
unsigned int len;
@@ -139,7 +105,7 @@ void playnextvid()
{
waitskip=0;
playing=queue.items[0].video;
- requester=queue.items[0].requester;
+ free(queue.items[0].requester);
free(queue.items[0].title);
--queue.itemcount;
memmove(queue.items, &queue.items[1], sizeof(struct queueitem)*queue.itemcount);
@@ -152,15 +118,13 @@ void playnextvid()
void playnext(int x)
{
free(playing);
- free(requester);
playing=0;
- requester=0;
if(queue.itemcount<1){alarm(0); printf("Nothing more to play\n"); return;} // Nothing more to play
if(!list_contains(&goodvids, queue.items[0].video))
{
if(!waitskip)
{
- say(0, "Next video (%s, %s) is not yet approved by mods\n", queue.items[0].video, queue.items[0].title);
+ say(0, "Next video (http://youtube.com/watch?v=%s) is not yet approved by mods\n", queue.items[0].video);
unsigned int i;
for(i=1; i<queue.itemcount; ++i)
{
@@ -183,51 +147,6 @@ void playnext(int x)
int main(int argc, char** argv)
{
- // Handle arguments (-d, -l, -h additions, the rest are handled by tc_client)
- char daemon=0;
- char* logfile=0;
- char verbose=0;
- unsigned int i;
- for(i=1; i<argc; ++i)
- {
- if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--daemon"))
- {
- daemon=1;
- // Remove non-tc_client argument
- --argc;
- memmove(&argv[i], &argv[i+1], sizeof(char*)*(argc-i));
- argv[argc]=0;
- --i;
- }
- else if(i+1<argc && (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log")))
- {
- logfile=argv[i+1];
- // Remove non-tc_client argument
- argc-=2;
- memmove(&argv[i], &argv[i+2], sizeof(char*)*(argc-i));
- argv[argc]=0;
- --i;
- }
- else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
- {
- verbose=1;
- // Remove non-tc_client argument
- --argc;
- memmove(&argv[i], &argv[i+1], sizeof(char*)*(argc-i));
- argv[argc]=0;
- --i;
- }
- else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
- {
- printf("Additional options for modbot:\n"
- "-d/--daemon = daemonize after startup\n"
- "-l/--log <file> = log output into <file>\n"
- "-v/--verbose = print/log all incoming messages\n"
- "\n");
- execv("./tc_client", argv);
- return 1;
- }
- }
int in[2];
int out[2];
pipe(in);
@@ -249,8 +168,6 @@ int main(int argc, char** argv)
list_load(&badvids, "badvids.txt");
char buf[1024];
int len=0;
- time_t sessionstart=time(0);
- time_modcount=sessionstart;
while(1)
{
if(read(out[0], &buf[len], 1)<1){break;}
@@ -283,22 +200,7 @@ int main(int argc, char** argv)
memmove(esc, &esc[len+1], strlen(&esc[len]));
}
len=0;
- // Note: daemonizing and setting up logging here to avoid interfering with account password entry
- if(daemon)
- {
- if(fork()){return 0;}
- daemon=0;
- if(!logfile){logfile="/dev/null";} // Prevent writing to stdout as a daemon
- }
- if(logfile)
- {
- int f=open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0600);
- dup2(f, 1);
- dup2(f, 2);
- close(f);
- logfile=0;
- }
- if(verbose){printf("Got line '%s'\n", buf); fflush(stdout);}
+ // printf("Got line '%s'\n", buf);
char* space=strchr(buf, ' ');
if(!space){continue;}
if(!strcmp(space, " is a moderator."))
@@ -306,14 +208,12 @@ int main(int argc, char** argv)
// If there are not-yet-approved videos in the queue when a mod joins, ask them to review them
space[0]=0;
list_add(&mods, buf);
- timemods();
continue;
}
if(!strcmp(space, " is no longer a moderator."))
{
space[0]=0;
list_del(&mods, buf);
- timemods();
continue;
}
space[0]=0;
@@ -339,24 +239,19 @@ int main(int argc, char** argv)
{
char title[256];
char vid[1024];
- char viderr[1024];
- getvidinfo(&msg[9], "--get-id", vid, viderr, 1024);
- if(!vid[0]) // Nothing found
- {
- say(pm, "No video found, sorry (%s)\n", viderr);
- continue;
- }
+ getvidinfo(&msg[9], "--get-id", vid, 1024);
+ if(!vid[0]){say(pm, "No video found, sorry\n"); continue;} // Nothing found
char* plist;
for(plist=vid; plist[0] && plist[0]!='\r' && plist[0]!='\n'; plist=&plist[1]);
if(plist[0]) // Link was a playlist, do some trickery to get the title of the first video (instead of getting nothing)
{
strcpy(title, "Playlist, starting with ");
plist[0]=0;
- getvidinfo(vid, "--get-title", &title[24], 0, 256-24);
+ getvidinfo(vid, "--get-title", &title[24], 256-24);
plist[0]='\n';
}else{
plist=0;
- getvidinfo(vid, "--get-title", title, 0, 256);
+ getvidinfo(vid, "--get-title", title, 256);
}
printf("Requested ID '%s' by '%s'\n", vid, nick);
// Check if it's already queued and mention which spot it's in, or if it's marked as bad and shouldn't be queued
@@ -402,20 +297,14 @@ int main(int argc, char** argv)
if(!strcmp(queue.items[i].requester, nick))
{
queue_del(&queue, queue.items[i].video);
- if(!playing && i==0){playnext(0);}
- i=1; // distinguish from just having reached the front of the queue
break;
}
}
- if(!i)
- {
- say(pm, "I can't find any request by you, sorry\n");
- }
}
else if(!strncmp(msg, "!wrongrequest ", 14))
{
char vid[1024];
- getvidinfo(&msg[14], "--get-id", vid, 0, 1024);
+ getvidinfo(&msg[14], "--get-id", vid, 1024);
unsigned int i;
for(i=0; i<queue.itemcount; ++i)
{
@@ -443,29 +332,22 @@ int main(int argc, char** argv)
{
char buf[len];
buf[0]=0;
- unsigned int listed=0;
for(i=0; i<queue.itemcount; ++i)
{
- if(listed<5 && !list_contains(&goodvids, queue.items[i].video))
+ if(!list_contains(&goodvids, queue.items[i].video))
{
if(buf[0]){strcat(buf, ", ");}
strcat(buf, queue.items[i].video);
strcat(buf, " (");
strcat(buf, queue.items[i].title);
strcat(buf, ")");
- ++listed;
}
}
- 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.":"");
+ say(pm, "%u videos in queue, %u of which are not yet approved by mods (%s)\n", queue.itemcount, notapproved, buf);
}else{
- say(pm, "%u video%s in queue\n", queue.itemcount, (queue.itemcount==1)?"":"s");
+ say(pm, "%u videos in queue\n", queue.itemcount);
}
}
- else if(!strcmp(msg, "!requestedby"))
- {
- if(!playing){say(pm, "Nothing is playing\n");}
- else{say(pm, "%s requested %s\n", requester, playing);}
- }
else if(!strcmp(msg, "!time")) // Debugging
{
unsigned int remaining=alarm(0);
@@ -474,40 +356,31 @@ int main(int argc, char** argv)
}
else if(!strcmp(msg, "!help"))
{
- say(pm, "http://tc_client.ion.nu/misc/modbotcommands.html\n");
- }
- else if(!strcmp(msg, "!modstats"))
- {
- unsigned int session=time(0)-sessionstart;
- timemods();
- unsigned int hasmods=time_with_mods*100/session;
- const char* timeformat="seconds";
- if(session>=120)
- {
- session/=60;
- timeformat="minutes";
- if(session>=120)
- {
- session/=60;
- timeformat="hours";
- if(session>=48)
- {
- session/=24;
- timeformat="days";
- }
- }
- }
- say(pm, "The channel has had mods %u%% of the time for the past %u %s\n", hasmods, session, timeformat);
- }
- else if(!strcmp(msg, "!syncvid"))
- {
- if(playing)
- {
- space[0]=0;
- say(0, "/priv %s /mbs youTube %s %u\n", nick, playing, (time(0)-started)*1000);
- }else{
- say(pm, "Nothing is playing\n");
- }
+ say(nick, "The following commands can be used:\n");
+ usleep(500000);
+ say(nick, "!request <link> = request a video to be played\n");
+ usleep(500000);
+ say(nick, "!queue = get the number of songs in queue and which (if any) need to be approved\n");
+ usleep(500000);
+ say(nick, "Mod commands:\n"); // TODO: don't bother filling non-mods' chats with these?
+ usleep(500000);
+ say(nick, "!playnext = play the next video in queue without approving it (to see if it's ok)\n");
+ usleep(500000);
+ say(nick, "!approve = mark the currently playing video as good, or if none is playing the next in queue\n");
+ usleep(500000);
+ say(nick, "!approve <link> = mark the specified video as okay\n");
+ usleep(500000);
+ say(nick, "!approve next = mark the next not yet approved video as okay\n");
+ usleep(500000);
+ say(nick, "!approve entire queue = approve all videos in queue (for playlists)\n");
+ usleep(500000);
+ say(nick, "!badvid = stop playing the current video and mark it as bad\n");
+ usleep(500000);
+ say(nick, "!badvid <link> = mark the specified video as bad, preventing it from ever being queued again\n");
+ usleep(500000);
+ say(nick, "!wrongrequest = undo the last request you made\n");
+ usleep(500000);
+ say(nick, "You can also just play videos manually/the good old way and they will be marked as good.\n");
}
else if(list_contains(&mods, nick)) // Mods-only commands
{
@@ -568,13 +441,13 @@ int main(int argc, char** argv)
{
list_save(&goodvids, "goodvids.txt");
if(!playing){playnext(0);} // Next in queue just got approved, so play it
- else{say(pm, "Queue approved. Make sure none of the videos were inappropriate\n");}
+ else{say(pm, "Queue approved\n");}
}else{
- say(pm, "The queue is already approved (or empty)\n");
+ say(0, "%s: there is nothing in the queue that isn't already approved, please do not overuse this function\n", nick);
}
continue;
}else{
- getvidinfo(vid, "--get-id", vidbuf, 0, 256);
+ getvidinfo(vid, "--get-id", vidbuf, 256);
vid=vidbuf;
}
list_add(&goodvids, vid);
@@ -590,24 +463,22 @@ int main(int argc, char** argv)
char vid[1024];
if(space && space[1])
{
- getvidinfo(&space[1], "--get-id", vid, 0, 256);
+ getvidinfo(&space[1], "--get-id", vid, 256);
}else{strncpy(vid, playing, 1023); vid[1023]=0;}
- if(!vid[0]){say(pm, "Video not found, sorry\n"); continue;}
+ if(!vid[0]){say(pm, "Video not found, sorry\n");}
queue_del(&queue, vid);
list_del(&goodvids, vid);
list_add(&badvids, vid);
list_save(&goodvids, "goodvids.txt");
list_save(&badvids, "badvids.txt");
- if(playing && !strcmp(vid, playing)){say(0, "/mbc youTube\n");}
- say(pm, "Marked '%s' as bad, it will not be allowed into the queue again. You can reverse this by !approve'ing the video by ID/link/name\n", vid);
- playnext(0);
+ if(!strcmp(vid, playing)){say(0, "/mbc youTube\n"); playnext(0);}
}
else if(!strcmp(msg, "!skip") || !strncmp(msg, "!skip ", 6))
{
unsigned int num=((msg[5]&&msg[6])?strtoul(&msg[6], 0, 0):1);
if(num<1){say(pm, "The given value evaluates to 0, please specify the number of videos you would like to skip (or if you do not specify it will default to 1)\n"); continue;}
if(playing){free(playing); playing=0; --num; say(0, "/mbc youTube\n");}
- while(num>0&&queue.itemcount>0)
+ while(num>0)
{
queue_del(&queue, queue.items[0].video);
--num;
@@ -625,18 +496,11 @@ int main(int argc, char** argv)
list_save(&goodvids, "goodvids.txt");
free(playing);
playing=strdup(vid);
- free(requester);
- requester=strdup(nick);
unsigned int pos=(end?(strtol(&end[1], 0, 0)/1000):0);
alarm(getduration(playing)-pos);
started=time(0)-pos;
}
- else if(!strcmp(msg, "/mbc youTube") && playing) // Video cancelled
- {
- list_del(&goodvids, playing); // manual /mbc is often used when !badvid should be used, so at least remove it from the list of approved videos
- list_save(&goodvids, "goodvids.txt");
- playnext(0);
- }
+ else if(!strcmp(msg, "/mbc youTube")){playnext(0);} // Video cancelled
else if(!strncmp(msg, "/mbsk youTube ", 14)) // Seeking
{
unsigned int pos=strtol(&msg[14], 0, 0)/1000;
@@ -673,12 +537,6 @@ int main(int argc, char** argv)
say(0, "/priv %s /mbs youTube %s %u\n", nick, playing, (time(0)-started)*1000);
}
}
- else if(!strcmp(space, " left the channel"))
- {
- space[0]=0;
- list_del(&mods, nick); // Absent people can't be mods
- timemods();
- }
}
}
}
diff --git a/utilities/stringutils.c b/utilities/stringutils.c
deleted file mode 100644
index e7d33b4..0000000
--- a/utilities/stringutils.c
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- A few simple string utilities
- 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 <string.h>
-
-int strcount(const char* haystack, const char* needle)
-{
- int c=0;
- haystack=strstr(haystack, needle);
- while(haystack)
- {
- ++c;
- haystack=strstr(&haystack[1], needle);
- }
- return c;
-}
diff --git a/utilities/stringutils.h b/utilities/stringutils.h
deleted file mode 100644
index c942bfe..0000000
--- a/utilities/stringutils.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- A few simple string utilities
- 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/>.
-*/
-extern int strcount(const char* haystack, const char* needle);