$ git clone http://tcclient.ion.nu/tc_client.git
commit 50efe1a0755d0ebce5f28b66741ec9cd2c3f8c76
Author: Alicia <...>
Date: Mon Jun 8 13:27:18 2015 +0200
tc_client-gtk and camviewer: added compatibility code to build on windows (for now the tc_client core still needs to be built with cygwin for windows)
diff --git a/ChangeLog b/ChangeLog
index 6500cce..ecde538 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
Fixed the /forgive command (by name) which would previously fail unless the target was found in the first 'banlist' AMF0 packet.
modbot: fixed finding the duration of videos longer than 59 minutes (conversion from hh:mm:ss format to seconds)
tc_client-gtk: redesigned the startup window to better support frequenting multiple channels.
+tc_client-gtk and camviewer: added compatibility code to build on windows (for now the tc_client core still needs to be built with cygwin for windows)
0.32:
Added an 'install' make target, adjusted utilities to run tc_client from PATH unless they were run from the build directory (i.e. './<executable>')
Provide feedback on the /ban command.
diff --git a/Makefile b/Makefile
index 14a27ac..b93e7bc 100644
--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,9 @@ 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 utilities/compat.o
MODBOT_OBJ=utilities/modbot/modbot.o utilities/list.o utilities/modbot/queue.o
-CAMVIEWER_OBJ=utilities/camviewer/camviewer.o
+CAMVIEWER_OBJ=utilities/camviewer/camviewer.o utilities/compat.o
CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o utilities/cursedchat/buffer.o utilities/compat.o utilities/list.o
-TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/config.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o
+TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/config.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o utilities/compat.o
UTILS=irchack modbot
ifdef GTK_LIBS
ifdef AVCODEC_LIBS
@@ -30,6 +30,12 @@ ifdef SWSCALE_LIBS
ifdef LIBV4L2_LIBS
CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
endif
+ ifneq ($(findstring MINGW,$(shell uname -s)),)
+ LDFLAGS+=-mwindows
+ windowstargets: camviewer tc_client-gtk
+ @echo
+ @echo 'To build the core (tc_client.exe), enter this directory from cygwin and type make'
+ endif
endif
endif
endif
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index ac52f75..9bbe689 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -18,7 +18,12 @@
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/prctl.h>
+#ifdef _WIN32
+ #include <wtypes.h>
+ #include <winbase.h>
+#else
+ #include <sys/prctl.h>
+#endif
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#if LIBAVUTIL_VERSION_MAJOR>50 || (LIBAVUTIL_VERSION_MAJOR==50 && LIBAVUTIL_VERSION_MINOR>37)
@@ -40,6 +45,7 @@
#include <libv4l2.h>
#include <linux/videodev2.h>
#endif
+#include "../compat.h"
#if GTK_MAJOR_VERSION==2
#define GTK_ORIENTATION_HORIZONTAL 0
@@ -55,6 +61,14 @@
}
#endif
+#ifdef _WIN32
+SECURITY_ATTRIBUTES sa={
+ .nLength=sizeof(SECURITY_ATTRIBUTES),
+ .bInheritHandle=1,
+ .lpSecurityDescriptor=0
+};
+#endif
+
struct camera
{
AVFrame* frame;
@@ -146,10 +160,12 @@ char buf[1024];
gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
struct viddata* data=datap;
+ gsize r;
unsigned 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;}
+ g_io_channel_read_chars(channel, &buf[i], 1, &r, 0);
+ if(r<1){printf("No more data\n"); gtk_main_quit(); return 0;}
if(buf[i]=='\r'||buf[i]=='\n'){break;}
}
buf[i]=0;
@@ -246,7 +262,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
unsigned int size=strtoul(&sizestr[1], 0, 0);
if(!size){return 1;}
unsigned char frameinfo;
- read(tc_client[0], &frameinfo, 1);
+ g_io_channel_read_chars(channel, (gchar*)&frameinfo, 1, 0, 0);
--size; // For the byte we read above
AVPacket pkt;
av_init_packet(&pkt);
@@ -256,7 +272,8 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
unsigned int pos=0;
while(pos<size)
{
- pos+=read(tc_client[0], pkt.data+pos, size-pos);
+ g_io_channel_read_chars(channel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
}
#if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
// Find the camera representation for the given ID (for decoder context)
@@ -297,13 +314,14 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
unsigned char databuf[size+4];
pkt.data=databuf;
unsigned char frameinfo;
- read(tc_client[0], &frameinfo, 1);
+ g_io_channel_read_chars(channel, (gchar*)&frameinfo, 1, 0, 0);
// 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(tc_client[0], pkt.data+pos, size-pos);
+ g_io_channel_read_chars(channel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
}
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;}
@@ -496,6 +514,29 @@ int main(int argc, char** argv)
#endif
gtk_widget_show_all(w);
+ unsigned int i;
+#ifdef _WIN32
+ HANDLE h_tc_client0, h_tc_client1;
+ CreatePipe(&h_tc_client0, &h_tc_client1, &sa, 0);
+ HANDLE h_tc_client_in0, h_tc_client_in1;
+ CreatePipe(&h_tc_client_in0, &h_tc_client_in1, &sa, 0);
+ tc_client[0]=_open_osfhandle(h_tc_client0, _O_RDONLY);
+ tc_client[1]=_open_osfhandle(h_tc_client1, _O_WRONLY);
+ tc_client_in[0]=_open_osfhandle(h_tc_client_in0, _O_RDONLY);
+ tc_client_in[1]=_open_osfhandle(h_tc_client_in1, _O_WRONLY);
+ STARTUPINFO startup;
+ GetStartupInfo(&startup);
+ startup.dwFlags|=STARTF_USESTDHANDLES;
+ startup.hStdInput=h_tc_client_in0;
+ startup.hStdOutput=h_tc_client1;
+ PROCESS_INFORMATION pi;
+ int len=strlen("./tc_client");
+ for(i=1; i<argc; ++i){len+=strlen(argv[i])+1;}
+ char cmd[len+1];
+ strcpy(cmd, "./tc_client");
+ for(i=1; i<argc; ++i){strcat(cmd, " "); strcat(cmd, argv[i]);}
+ CreateProcess(0, cmd, 0, 0, 1, DETACHED_PROCESS, 0, 0, &startup, &pi);
+#else
pipe(tc_client);
pipe(tc_client_in);
if(!fork())
@@ -508,6 +549,7 @@ int main(int argc, char** argv)
argv[0]=(strncmp(argv[0], "./", 2)?"tc_client":"./tc_client");
execvp(argv[0], argv);
}
+#endif
close(tc_client_in[0]);
GIOChannel* tcchannel=g_io_channel_unix_new(tc_client[0]);
g_io_channel_set_encoding(tcchannel, 0, 0);
@@ -515,9 +557,11 @@ int main(int argc, char** argv)
gtk_main();
+#ifdef _WIN32
+ TerminateProcess(pi.hProcess, 0);
+#endif
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);
diff --git a/utilities/compat.c b/utilities/compat.c
index e620559..ba15f60 100644
--- a/utilities/compat.c
+++ b/utilities/compat.c
@@ -15,8 +15,10 @@
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
+#if defined(__ANDROID__) || defined(_WIN32)
+// Android and windows have no dprintf, so we make our own
+#include <stdio.h>
+#include <unistd.h>
#include <stdarg.h>
size_t dprintf(int fd, const char* fmt, ...)
{
diff --git a/utilities/compat.h b/utilities/compat.h
index 2cfb39b..f635886 100644
--- a/utilities/compat.h
+++ b/utilities/compat.h
@@ -14,8 +14,16 @@
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__
+#if defined(__ANDROID__) || defined(_WIN32)
#include <stdint.h>
+#include <stddef.h>
extern size_t dprintf(int fd, const char* fmt, ...);
#define mbtowc(x,y,z) 1
#endif
+#ifdef _WIN32
+ #define prctl(...)
+ #define wait(x)
+#endif
+#if GLIB_MAJOR_VERSION<2 || (GLIB_MAJOR_VERSION==2 && GLIB_MINOR_VERSION<2)
+ #define g_io_channel_read_chars(a,b,c,d,e) g_io_channel_read(a,b,c,d)
+#endif
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index c5a7514..037f41a 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -19,7 +19,13 @@
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/prctl.h>
+#ifdef _WIN32
+ #include <wtypes.h>
+ #include <winbase.h>
+#else
+ #include <sys/prctl.h>
+ #include <sys/wait.h>
+#endif
#include <ctype.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
@@ -50,6 +56,7 @@
#include "gui.h"
#include "logging.h"
#include "../stringutils.h"
+#include "../compat.h"
struct viddata
{
@@ -78,6 +85,9 @@ const char* mycolor=0;
char* nickname=0;
char frombuild=0; // Running from the build directory
#define TC_CLIENT (frombuild?"./tc_client":"tc_client")
+#ifdef _WIN32
+ PROCESS_INFORMATION coreprocess={.hProcess=0};
+#endif
void updatescaling(struct viddata* data, unsigned int width, unsigned int height)
{
@@ -148,11 +158,12 @@ void printchat_color(struct viddata* data, const char* text, const char* color,
char buf[1024];
gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
{
- int fd=g_io_channel_unix_get_fd(iochannel);
+ gsize r;
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;}
+ g_io_channel_read_chars(iochannel, &buf[i], 1, &r, 0);
+ if(r<1){printf("No more data\n"); gtk_main_quit(); return 0;}
if(buf[i]=='\r'||buf[i]=='\n'){break;}
}
buf[i]=0;
@@ -222,6 +233,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
if(space[-1]==':')
{
// TODO: handle /msg (PMs)
+#ifndef _WIN32 // TODO: port sound and youtube command code to windows
if(config_get_bool("soundradio_cmd") && !fork())
{
execlp("sh", "sh", "-c", config_get_str("soundcmd"), (char*)0);
@@ -229,7 +241,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
}
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: store the PID and make sure it's dead before starting a new video? and upon /mbc?
// TODO: only play videos from mods?
char* id=&space[14];
char* offset=strchr(id, ' ');
@@ -256,6 +268,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
execlp("sh", "sh", "-c", cmd, (char*)0);
_exit(0);
}
+#endif
}
// TODO: handle logging PMs
if(config_get_bool("enable_logging")){logger_write(buf, channel, 0);}
@@ -405,7 +418,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
unsigned int size=strtoul(&sizestr[1], 0, 0);
if(!size){return 1;}
unsigned char frameinfo;
- read(fd, &frameinfo, 1);
+ g_io_channel_read_chars(iochannel, (gchar*)&frameinfo, 1, 0, 0);
--size; // For the byte we read above
AVPacket pkt;
av_init_packet(&pkt);
@@ -415,7 +428,8 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
unsigned int pos=0;
while(pos<size)
{
- pos+=read(fd, pkt.data+pos, size-pos);
+ g_io_channel_read_chars(iochannel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
}
#if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
// Find the camera representation for the given ID (for decoder context)
@@ -448,13 +462,14 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
unsigned char databuf[size+4];
pkt.data=databuf;
unsigned char frameinfo;
- read(fd, &frameinfo, 1);
+ g_io_channel_read_chars(iochannel, (gchar*)&frameinfo, 1, 0, 0);
// 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);
+ g_io_channel_read_chars(iochannel, (gchar*)pkt.data+pos, size-pos, &r, 0);
+ pos+=r;
}
if(!strcmp(&buf[7], "out"))
{
@@ -608,7 +623,9 @@ gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddat
{
updatescaling(data, event->width, 0);
}
+#ifndef _WIN32 // For some reason scrolling as a response to resizing freezes windows
if(bottom){autoscroll_after(data->scroll);}
+#endif
return 0;
}
@@ -616,7 +633,9 @@ 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)));
+#ifndef _WIN32
if(bottom){autoscroll_after(data->scroll);}
+#endif
}
gboolean inputkeys(GtkWidget* widget, GdkEventKey* event, void* data)
@@ -741,6 +760,36 @@ void startsession(GtkButton* button, void* x)
if(!chanpass[0]){chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")));}
const char* acc_user=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_username")));
const char* acc_pass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "acc_password")));
+#ifdef _WIN32
+ HANDLE h_tc_client0, h_tc_client1;
+ CreatePipe(&h_tc_client0, &h_tc_client1, &sa, 0);
+ HANDLE h_tc_client_in0, h_tc_client_in1;
+ CreatePipe(&h_tc_client_in0, &h_tc_client_in1, &sa, 0);
+ tc_client[0]=_open_osfhandle(h_tc_client0, _O_RDONLY);
+ tc_client[1]=_open_osfhandle(h_tc_client1, _O_WRONLY);
+ tc_client_in[0]=_open_osfhandle(h_tc_client_in0, _O_RDONLY);
+ tc_client_in[1]=_open_osfhandle(h_tc_client_in1, _O_WRONLY);
+ STARTUPINFO startup;
+ GetStartupInfo(&startup);
+ startup.dwFlags|=STARTF_USESTDHANDLES;
+ startup.hStdInput=h_tc_client_in0;
+ startup.hStdOutput=h_tc_client1;
+ char cmd[strlen("./tc_client -u 0")+strlen(acc_user)+strlen(channel)+strlen(nick)+strlen(chanpass)];
+ strcpy(cmd, "./tc_client ");
+ if(acc_user[0])
+ {
+ strcat(cmd, "-u ");
+ strcat(cmd, acc_user);
+ strcat(cmd, " ");
+ }
+ strcat(cmd, channel);
+ strcat(cmd, " ");
+ strcat(cmd, nick);
+ strcat(cmd, " ");
+ strcat(cmd, chanpass);
+ strcat(cmd, " ");
+ CreateProcess(0, cmd, 0, 0, 1, DETACHED_PROCESS, 0, 0, &startup, &coreprocess);
+#else
pipe(tc_client);
pipe(tc_client_in);
if(!fork())
@@ -757,6 +806,7 @@ void startsession(GtkButton* button, void* x)
execlp(TC_CLIENT, TC_CLIENT, channel, nick, chanpass, (char*)0);
}
}
+#endif
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]);
@@ -772,7 +822,11 @@ int main(int argc, char** argv)
avcodec_register_all();
data->vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
data->adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
+#ifndef _WIN32
signal(SIGCHLD, SIG_IGN);
+#else
+ frombuild=1;
+#endif
#if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
#ifdef HAVE_AVRESAMPLE
@@ -913,6 +967,12 @@ int main(int argc, char** argv)
gtk_main();
+#ifdef _WIN32
+ if(coreprocess.hProcess)
+ {
+ TerminateProcess(coreprocess.hProcess, 0);
+ }
+#endif
camera_cleanup();
#ifdef HAVE_AVRESAMPLE
avresample_free(&data->resamplectx);
diff --git a/utilities/gtk/compat.c b/utilities/gtk/compat.c
index 3f2efd4..8d37d3d 100644
--- a/utilities/gtk/compat.c
+++ b/utilities/gtk/compat.c
@@ -22,6 +22,14 @@
#include <gtk/gtk.h>
#include "compat.h"
+#ifdef _WIN32
+SECURITY_ATTRIBUTES sa={
+ .nLength=sizeof(SECURITY_ATTRIBUTES),
+ .bInheritHandle=1,
+ .lpSecurityDescriptor=0
+};
+#endif
+
#if GTK_MAJOR_VERSION<3
GtkWidget* gtk_box_new(int vertical, int spacing)
{
@@ -92,4 +100,11 @@
if(!gtk_builder_add_from_string(gui, buf, -1, &error)){g_error("%s\n", error->message);}
return gui;
}
+#elif GTK_MAJOR_VERSION==3 && GTK_MINOR_VERSION<10
+ GtkBuilder* gtk_builder_new_from_file(const char* filename)
+ {
+ GtkBuilder* gui=gtk_builder_new();
+ gtk_builder_add_from_file(gui, filename, 0);
+ return gui;
+ }
#endif
diff --git a/utilities/gtk/compat.h b/utilities/gtk/compat.h
index a0a5ee2..cd61c08 100644
--- a/utilities/gtk/compat.h
+++ b/utilities/gtk/compat.h
@@ -14,14 +14,18 @@
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 _WIN32
+#include <wtypes.h>
+extern SECURITY_ATTRIBUTES sa;
+#endif
#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
-#if GTK_MAJOR_VERSION<3 || GTK_MINOR_VERSION<10
+#if GTK_MAJOR_VERSION<3 || (GTK_MAJOR_VERSION==3 && GTK_MINOR_VERSION<10)
#define gtk_button_new_from_icon_name(name, size) gtk_button_new_from_stock(name)
+ extern GtkBuilder* gtk_builder_new_from_file(const char* filename);
#endif
diff --git a/utilities/gtk/config.c b/utilities/gtk/config.c
index 7d5a813..d14ac6e 100644
--- a/utilities/gtk/config.c
+++ b/utilities/gtk/config.c
@@ -31,9 +31,13 @@ unsigned int configitemcount=0;
void config_load(void)
{
+#ifndef _WIN32
const char* home=getenv("HOME");
char filename[strlen(home)+strlen("/.config/tc_client-gtk0")];
sprintf(filename, "%s/.config/tc_client-gtk", home);
+#else
+ char* filename="config.txt";
+#endif
FILE* f=fopen(filename, "r");
if(!f){return;}
char buf[2048];
@@ -55,11 +59,15 @@ void config_load(void)
void config_save(void)
{
+#ifndef _WIN32
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");
+#else
+ char* filename="config.txt";
+#endif
FILE* f=fopen(filename, "w");
if(!f){perror("fopen(~/.config/tc_client-gtk)"); return;}
unsigned int i;
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index ded7028..dad17fe 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -16,6 +16,7 @@
*/
#include <stdlib.h>
#include <string.h>
+#include <stdint.h>
#include <gtk/gtk.h>
#include "gui.h"
#include "config.h"