From fcb22b3d1770e7f441385198cde6ddda3953e38b Mon Sep 17 00:00:00 2001 From: John Hodge Date: Sun, 18 May 2014 00:20:56 +0800 Subject: [PATCH] Usermode/irc - Rework into multiple files --- Usermode/Applications/irc_src/Makefile | 3 +- Usermode/Applications/irc_src/common.h | 20 + Usermode/Applications/irc_src/input.c | 160 ++++ Usermode/Applications/irc_src/input.h | 12 + Usermode/Applications/irc_src/main.c | 716 +----------------- Usermode/Applications/irc_src/message.h | 37 + Usermode/Applications/irc_src/pseudo_curses.c | 30 + Usermode/Applications/irc_src/pseudo_curses.h | 13 + Usermode/Applications/irc_src/server.c | 340 +++++++++ Usermode/Applications/irc_src/server.h | 29 + Usermode/Applications/irc_src/window.c | 243 ++++++ Usermode/Applications/irc_src/window.h | 36 + 12 files changed, 953 insertions(+), 686 deletions(-) create mode 100644 Usermode/Applications/irc_src/common.h create mode 100644 Usermode/Applications/irc_src/input.c create mode 100644 Usermode/Applications/irc_src/input.h create mode 100644 Usermode/Applications/irc_src/message.h create mode 100644 Usermode/Applications/irc_src/pseudo_curses.c create mode 100644 Usermode/Applications/irc_src/pseudo_curses.h create mode 100644 Usermode/Applications/irc_src/server.c create mode 100644 Usermode/Applications/irc_src/server.h create mode 100644 Usermode/Applications/irc_src/window.c create mode 100644 Usermode/Applications/irc_src/window.h diff --git a/Usermode/Applications/irc_src/Makefile b/Usermode/Applications/irc_src/Makefile index 9fab0600..f22e82fe 100644 --- a/Usermode/Applications/irc_src/Makefile +++ b/Usermode/Applications/irc_src/Makefile @@ -4,7 +4,8 @@ LDFLAGS += -lnet -lreadline -OBJ = main.o +OBJ = main.o server.o input.o +OBJ += window.o pseudo_curses.o BIN = irc -include ../Makefile.tpl diff --git a/Usermode/Applications/irc_src/common.h b/Usermode/Applications/irc_src/common.h new file mode 100644 index 00000000..e374328b --- /dev/null +++ b/Usermode/Applications/irc_src/common.h @@ -0,0 +1,20 @@ +/* + */ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include "pseudo_curses.h" + +typedef struct sServer tServer; + +extern void _SysDebug(const char *format, ...); + +extern int writef(int FD, const char *Format, ...); +extern int OpenTCP(const char *AddressString, short PortNumber); +extern char *GetValue(char *Src, int *Ofs); + +extern void Redraw_Screen(void); +extern void Exit(const char *Reason) __attribute__((noreturn)); + +#endif + diff --git a/Usermode/Applications/irc_src/input.c b/Usermode/Applications/irc_src/input.c new file mode 100644 index 00000000..2236152a --- /dev/null +++ b/Usermode/Applications/irc_src/input.c @@ -0,0 +1,160 @@ +/* + */ +#include "input.h" +#include "window.h" +#include "server.h" +#include +#include +#include +#include + +// === PROTOTYPES === +void Input_FillSelect(int *nfds, fd_set *rfds); +void Input_HandleSelect(int nfds, const fd_set *rfds); + int ParseUserCommand(char *String); + +// === GLOBALS === +tReadline *gpInput_ReadlineInfo; + +// === CODE === +void Input_FillSelect(int *nfds, fd_set *rfds) +{ + if( !gpInput_ReadlineInfo ) { + gpInput_ReadlineInfo = Readline_Init(1); + } + + FD_SET(0, rfds); + if(*nfds < 0+1) + *nfds = 0+1; +} + +void Input_HandleSelect(int nfds, const fd_set *rfds) +{ + // User input + if(FD_ISSET(0, rfds)) + { + char *cmd = Readline_NonBlock(gpInput_ReadlineInfo); + if( cmd ) + { + if( cmd[0] ) + { + ParseUserCommand(cmd); + } + free(cmd); + // Prompt + SetCursorPos(giTerminal_Height-1, 1); + printf("\x1B[2K"); // Clear line + printf("[%s]", Window_GetName(NULL)); + } + } +} + +void Cmd_join(char *ArgString) +{ + int pos=0; + char *channel_name = GetValue(ArgString, &pos); + + tServer *srv = Window_GetServer(NULL); + + if( srv ) + { + Windows_SwitchTo( Window_Create(srv, channel_name) ); + Redraw_Screen(); + Server_SendCommand(srv, "JOIN :%s", channel_name); + } +} + +void Cmd_quit(char *ArgString) +{ + const char *quit_message = ArgString; + if( quit_message == NULL || quit_message[0] == '\0' ) + quit_message = "/quit - Acess2 IRC Client"; + + Servers_CloseAll(quit_message); + + Exit(NULL); // NULL = user requested +} + +void Cmd_window(char *ArgString) +{ + int pos = 0; + char *window_id = GetValue(ArgString, &pos); + int window_num = atoi(window_id); + + if( window_num > 0 ) + { + // Get `window_num`th window + tWindow *win = Windows_GetByIndex(window_num-1); + if( win ) + { + Windows_SwitchTo( win ); + } + else + { + // Otherwise, silently ignore + } + } + else + { + window_num = 1; + for( tWindow *win; (win = Windows_GetByIndex(window_num-1)); window_num ++ ) + { + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, NULL, "%i: %s", window_num, Window_GetName(win)); + } + } +} + +const struct { + const char *Name; + void (*Fcn)(char *ArgString); +} caCommands[] = { + {"join", Cmd_join}, + {"quit", Cmd_quit}, + {"window", Cmd_window}, + {"win", Cmd_window}, + {"w", Cmd_window}, +}; +const int ciNumCommands = sizeof(caCommands)/sizeof(caCommands[0]); + +/** + * \brief Handle a line from the prompt + */ +int ParseUserCommand(char *String) +{ + if( String[0] == '/' ) + { + char *command; + int pos = 0; + + command = GetValue(String, &pos)+1; + + // TODO: Prefix matches + int cmdIdx = -1; + for( int i = 0; i < ciNumCommands; i ++ ) + { + if( strcmp(command, caCommands[i].Name) == 0 ) { + cmdIdx = i; + break; + } + } + if( cmdIdx != -1 ) { + caCommands[cmdIdx].Fcn(String+pos); + } + else + { + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, NULL, "Unknown command %s", command); + } + } + else + { + // Message + // - Only send if server is valid and window name is non-empty + tServer *srv = Window_GetServer(NULL); + if( srv && Window_IsChat(NULL) ) { + Window_AppendMessage(NULL, MSG_CLASS_MESSAGE, Server_GetNick(srv), "%s", String); + Server_SendCommand(srv, "PRIVMSG %s :%s\n", Window_GetName(NULL), String); + } + } + + return 0; +} diff --git a/Usermode/Applications/irc_src/input.h b/Usermode/Applications/irc_src/input.h new file mode 100644 index 00000000..dee859ee --- /dev/null +++ b/Usermode/Applications/irc_src/input.h @@ -0,0 +1,12 @@ +/* + */ +#ifndef _INPUT_H_ +#define _INPUT_H_ + +#include + +extern void Input_FillSelect(int *nfds, fd_set *rfds); +extern void Input_HandleSelect(int nfds, const fd_set *rfds); + +#endif + diff --git a/Usermode/Applications/irc_src/main.c b/Usermode/Applications/irc_src/main.c index 1ca53c2b..e3e2af48 100755 --- a/Usermode/Applications/irc_src/main.c +++ b/Usermode/Applications/irc_src/main.c @@ -6,94 +6,41 @@ #include #include #include -#include -#include #include -// === TYPES === -typedef struct sServer { - struct sServer *Next; - int FD; - char InBuf[BUFSIZ+1]; - int ReadPos; - char Name[]; -} tServer; - -typedef struct sMessage -{ - struct sMessage *Next; - time_t Timestamp; - tServer *Server; - int Type; - char *Source; // Pointer into `Data` - char Data[]; -} tMessage; +#include "common.h" +#include "input.h" +#include "window.h" +#include "server.h" -typedef struct sWindow -{ - struct sWindow *Next; - tMessage *Messages; - tServer *Server; //!< Canonical server (can be NULL) - int ActivityLevel; - char Name[]; // Channel name / remote user -} tWindow; - -enum eMessageTypes -{ - MSG_TYPE_NULL, - MSG_TYPE_SERVER, // Server message - - MSG_TYPE_NOTICE, // NOTICE command - MSG_TYPE_JOIN, // JOIN command - MSG_TYPE_PART, // PART command - MSG_TYPE_QUIT, // QUIT command - - MSG_TYPE_STANDARD, // Standard line - MSG_TYPE_ACTION, // /me - - MSG_TYPE_UNK -}; +// === TYPES === // === PROTOTYPES === int main(int argc, const char *argv[], const char *envp[]); int MainLoop(void); int ParseArguments(int argc, const char *argv[]); - int ParseUserCommand(char *String); // --- -tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber); -tMessage *Message_AppendF(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message, ...) __attribute__((format(__printf__,5,6))); -tMessage *Message_Append(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message); -tWindow *Window_Create(tServer *Server, const char *Name); void Redraw_Screen(void); - - int ProcessIncoming(tServer *Server); // --- Helpers -void SetCursorPos(int Row, int Col); +void Exit(const char *Reason); int writef(int FD, const char *Format, ...); int OpenTCP(const char *AddressString, short PortNumber); char *GetValue(char *Str, int *Ofs); -static inline int isdigit(int ch); // === GLOBALS === -char *gsUsername = "user"; -char *gsHostname = "acess"; -char *gsRealName = "Acess2 IRC Client"; -char *gsNickname = "acess"; -tServer *gpServers; -tWindow gWindow_Status = { - NULL, NULL, NULL, // No next, empty list, no server - 0, {""} // No activity, empty name (rendered as status) -}; -tWindow *gpWindows = &gWindow_Status; -tWindow *gpCurrentWindow = &gWindow_Status; - int giTerminal_Width = 80; - int giTerminal_Height = 25; +const char *gsExitReason = "No reason [BUG]"; // ==== CODE ==== void ExitHandler(void) { printf("\x1B[?1047l"); - printf("Quit\n"); + printf("Quit: %s\n", gsExitReason); +} + +void Exit(const char *Reason) +{ + gsExitReason = (Reason ? Reason : "User Requested"); + exit( (Reason ? 1 : 0) ); } int main(int argc, const char *argv[], const char *envp[]) @@ -105,40 +52,31 @@ int main(int argc, const char *argv[], const char *envp[]) atexit(ExitHandler); - if( _SysIOCtl(1, DRV_IOCTL_TYPE, NULL) != DRV_TYPE_TERMINAL ) { - fprintf(stderr, "note: assuming 80x25, can't get terminal dimensions\n"); - giTerminal_Width = 80; - giTerminal_Height = 25; - } - else { - struct ptydims dims; - _SysIOCtl(1, PTY_IOCTL_GETDIMS, &dims); - giTerminal_Width = dims.W; - giTerminal_Height = dims.H; - } + ACurses_Init(); printf("\x1B[?1047h"); - printf("\x1B[%i;%ir", 0, giTerminal_Height-1); + printf("\x1B[%i;%ir", 1, giTerminal_Height-1); SetCursorPos(giTerminal_Height-1, 1); printf("[(status)] "); // HACK: Static server entry // UCC (University [of Western Australia] Computer Club) IRC Server - gWindow_Status.Server = Server_Connect( "UCC", "130.95.13.18", 6667 ); + tServer *starting_server = Server_Connect( "UCC", "130.95.13.18", 6667 ); // Freenode (#osdev) // gWindow_Status.Server = Server_Connect( "Freenode", "89.16.176.16", 6667 ); // Local servers // gWindow_Status.Server = Server_Connect( "VMHost", "10.0.2.2", 6667 ); // gWindow_Status.Server = Server_Connect( "BitlBee", "192.168.1.39", 6667 ); - if( !gWindow_Status.Server ) + if( !starting_server ) return -1; - MainLoop(); + Windows_SetStatusServer(starting_server); - for( tServer *srv = gpServers; srv; srv = srv->Next ) - _SysClose(srv->FD); + MainLoop(); + + Servers_CloseAll("Client closing"); return 0; } @@ -149,73 +87,25 @@ int MainLoop(void) printf("[(status)] "); fflush(stdout); - tReadline *readline_info = Readline_Init(1); - for( ;; ) { fd_set readfds, errorfds; - int maxFD = 0; + int nfds = 1; FD_ZERO(&readfds); FD_ZERO(&errorfds); - FD_SET(0, &readfds); // stdin - fflush(stdout); + Input_FillSelect(&nfds, &readfds); + Servers_FillSelect(&nfds, &readfds, &errorfds); - // Fill server FDs in fd_set - for( tServer *srv = gpServers; srv; srv = srv->Next ) - { - FD_SET(srv->FD, &readfds); - FD_SET(srv->FD, &errorfds); - if( srv->FD > maxFD ) - maxFD = srv->FD; - } + int rv = _SysSelect(nfds, &readfds, 0, &errorfds, NULL, 0); + if( rv < 0 ) break; - int rv = _SysSelect(maxFD+1, &readfds, 0, &errorfds, NULL, 0); - if( rv == -1 ) break; - - if(FD_ISSET(0, &readfds)) - { - // User input - char *cmd = Readline_NonBlock(readline_info); - if( cmd ) - { - if( cmd[0] ) - { - ParseUserCommand(cmd); - } - free(cmd); - // Prompt - SetCursorPos(giTerminal_Height-1, 1); - printf("\x1B[2K"); // Clear line - if( gpCurrentWindow->Name[0] ) - printf("[%s:%s] ", - gpCurrentWindow->Server->Name, gpCurrentWindow->Name); - else - printf("[(status)] "); - } - } + // user input + Input_HandleSelect(nfds, &readfds); // Server response - for( tServer *srv = gpServers; srv; srv = srv->Next ) - { - if(FD_ISSET(srv->FD, &readfds)) - { - if( ProcessIncoming(srv) != 0 ) { - // Oops, error - _SysDebug("ProcessIncoming failed on FD%i (Server %p %s)", - srv->FD, srv, srv->Name); - return 1; - } - } - - if(FD_ISSET(srv->FD, &errorfds)) - { - _SysDebug("Error on FD%i (Server %p %s)", - srv->FD, srv, srv->Name); - return 1; - } - } + Servers_HandleSelect(nfds, &readfds, &errorfds); } return 0; } @@ -228,547 +118,12 @@ int ParseArguments(int argc, const char *argv[]) return 0; } - -void Cmd_join(char *ArgString) -{ - int pos=0; - char *channel_name = GetValue(ArgString, &pos); - - if( gpCurrentWindow->Server ) - { - gpCurrentWindow = Window_Create(gpCurrentWindow->Server, channel_name); - Redraw_Screen(); - writef(gpCurrentWindow->Server->FD, "JOIN :%s\n", channel_name); - } -} - -void Cmd_quit(char *ArgString) -{ - const char *quit_message = ArgString; - if( quit_message == NULL || quit_message[0] == '\0' ) - quit_message = "/quit - Acess2 IRC Client"; - - for( tServer *srv = gpServers; srv; srv = srv->Next ) - { - writef(srv->FD, "QUIT :%s\n", quit_message); - } - - exit(0); -} - -void Cmd_window(char *ArgString) -{ - int pos = 0; - char *window_id = GetValue(ArgString, &pos); - int window_num = atoi(window_id); - - if( window_num > 0 ) - { - tWindow *win; - window_num --; // Move to base 0 - // Get `window_num`th window - for( win = gpWindows; win && window_num--; win = win->Next ); - if( win ) { - gpCurrentWindow = win; - Redraw_Screen(); - } - // Otherwise, silently ignore - } - else - { - window_num = 1; - for( tWindow *win = gpWindows; win; win = win->Next, window_num ++ ) - { - if( win->Name[0] ) { - Message_AppendF(NULL, MSG_TYPE_SERVER, "client", "", - "%i: %s/%s", window_num, win->Server->Name, win->Name); - } - else { - Message_AppendF(NULL, MSG_TYPE_SERVER, "client", "", - "%i: (status)", window_num); - } - } - } -} - -const struct { - const char *Name; - void (*Fcn)(char *ArgString); -} caCommands[] = { - {"join", Cmd_join}, - {"quit", Cmd_quit}, - {"window", Cmd_window}, - {"win", Cmd_window}, - {"w", Cmd_window}, -}; -const int ciNumCommands = sizeof(caCommands)/sizeof(caCommands[0]); - -/** - * \brief Handle a line from the prompt - */ -int ParseUserCommand(char *String) -{ - if( String[0] == '/' ) - { - char *command; - int pos = 0; - - command = GetValue(String, &pos)+1; - - // TODO: Prefix matches - int cmdIdx = -1; - for( int i = 0; i < ciNumCommands; i ++ ) - { - if( strcmp(command, caCommands[i].Name) == 0 ) { - cmdIdx = i; - break; - } - } - if( cmdIdx != -1 ) { - caCommands[cmdIdx].Fcn(String+pos); - } - else - { - Message_AppendF(NULL, MSG_TYPE_SERVER, "client", "", "Unknown command %s", command); - } - } - else - { - // Message - // - Only send if server is valid and window name is non-empty - if( gpCurrentWindow->Server && gpCurrentWindow->Name[0] ) - { - Message_Append(gpCurrentWindow->Server, MSG_TYPE_STANDARD, - gsNickname, gpCurrentWindow->Name, String); - writef(gpCurrentWindow->Server->FD, - "PRIVMSG %s :%s\n", gpCurrentWindow->Name, - String - ); - } - } - - return 0; -} - -/** - * \brief Connect to a server - */ -tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber) -{ - tServer *ret; - - ret = calloc(1, sizeof(tServer) + strlen(Name) + 1); - - strcpy(ret->Name, Name); - - // Connect to the remove server - ret->FD = OpenTCP( AddressString, PortNumber ); - if( ret->FD == -1 ) { - fprintf(stderr, "%s: Unable to create socket\n", Name); - return NULL; - } - - // Append to open list - ret->Next = gpServers; - gpServers = ret; - - // Read some initial data - Message_Append(NULL, MSG_TYPE_SERVER, Name, "", "Connection opened"); - ProcessIncoming(ret); - - // Identify - writef(ret->FD, "USER %s %s %s : %s\n", gsUsername, gsHostname, AddressString, gsRealName); - writef(ret->FD, "NICK %s\n", gsNickname); - Message_Append(NULL, MSG_TYPE_SERVER, Name, "", "Identified"); - //printf("%s: Identified\n", Name); - - return ret; -} - -tMessage *Message_AppendF(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message, ...) -{ - va_list args; - int len; - va_start(args, Message); - len = vsnprintf(NULL, 0, Message, args); - va_end(args); - - char buf[len+1]; - va_start(args, Message); - vsnprintf(buf, len+1, Message, args); - va_end(args); - - return Message_Append(Server, Type, Source, Dest, buf); -} - -tMessage *Message_Append(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message) -{ - tWindow *win = NULL; - int msgLen = strlen(Message); - - // Server==NULL indicates an internal message - if( Server == NULL || Source[0] == '\0' ) - { - win = &gWindow_Status; - } - // Determine if it's a channel or PM - else if( Dest[0] == '#' || Dest[0] == '&' ) // TODO: Better determining here - { - for(win = gpWindows; win; win = win->Next) - { - if( win->Server == Server && strcmp(win->Name, Dest) == 0 ) - { - break; - } - } - if( !win ) { - //win = Window_Create(Server, Dest); - win = &gWindow_Status; // Stick it in the status window, just in case - } - } - #if 0 - else if( strcmp(Dest, Server->Nick) != 0 ) - { - // Umm... message for someone who isn't us? - win = &gWindow_Status; // Stick it in the status window, just in case - } - #endif - // Server message? - else if( strchr(Source, '.') ) // TODO: And again, less hack please - { - #if 1 - for(win = gpWindows; win; win = win->Next) - { - if( win->Server == Server && strcmp(win->Name, Source) == 0 ) - { - break; - } - } - #endif - if( !win ) { - win = &gWindow_Status; - } - - // Set source to the server name (instead of the hostname) - Source = Server->Name; - } - // Private message - else - { - for(win = gpWindows; win; win = win->Next) - { - if( win->Server == Server && strcmp(win->Name, Source) == 0 ) - { - break; - } - } - if( !win ) { - win = Window_Create(Server, Dest); - } - } - - // Create message cache - _SysDebug("Win (%s) msg: <%s> %s", win->Name, Source, Message); - tMessage *ret; - ret = malloc( sizeof(tMessage) + msgLen + 1 + strlen(Source) + 1 ); - ret->Source = ret->Data + msgLen + 1; - strcpy(ret->Source, Source); - strcpy(ret->Data, Message); - ret->Type = Type; - ret->Server = Server; - - // Append to window message list - ret->Next = win->Messages; - win->Messages = ret; - - // Print now if current window - if( win == gpCurrentWindow ) - { - printf("\33[s"); - printf("\33[T"); // Scroll down 1 (free space below) - SetCursorPos(giTerminal_Height-2, 1); - int prefixlen = strlen(Source) + 3; - int avail = giTerminal_Width - prefixlen; - int msglen = strlen(Message); - printf("[%s] %.*s", Source, avail, Message); - while( msglen > avail ) { - msglen -= avail; - Message += avail; - printf("\33[T"); - SetCursorPos(giTerminal_Height-2, prefixlen+1); - printf("%.*s", avail, Message); - } - printf("\x1b[u"); - } - - return ret; -} - -tWindow *Window_Create(tServer *Server, const char *Name) -{ - tWindow *ret, *prev = NULL; - int num = 1; - - // Get the end of the list - // TODO: Cache this instead - for( ret = gpCurrentWindow; ret; prev = ret, ret = ret->Next ) - num ++; - - ret = malloc(sizeof(tWindow) + strlen(Name) + 1); - ret->Messages = NULL; - ret->Server = Server; - ret->ActivityLevel = 1; - strcpy(ret->Name, Name); - - if( prev ) { - ret->Next = prev->Next; - prev->Next = ret; - } - else { // Shouldn't happen really - ret->Next = gpWindows; - gpWindows = ret; - } - -// printf("Win %i %s:%s created\n", num, Server->Name, Name); - - return ret; -} - void Redraw_Screen(void) { - int y = 0; - tMessage *msg; - printf("\x1B[2J"); // Clear screen printf("\x1B[0;0H"); // Reset cursor - msg = gpCurrentWindow->Messages; - - // TODO: Title bar? - - // Note: This renders from the bottom up - for( y = giTerminal_Height - 1; y -- && msg; msg = msg->Next) - { - int msglen = strlen(msg->Data); - int prefix_len = 3 + strlen(msg->Source); - int line_avail = giTerminal_Width - prefix_len; - int i = 0, done = 0; - - y -= msglen / line_avail; // Extra lines (y-- above handles the 1 line case) - SetCursorPos(y, 1); - printf("[%s] ", msg->Source); - - while(done < msglen) { - done += printf("%.*s", line_avail, msg->Data+done); - i ++; - SetCursorPos(y+i, prefix_len+1); - } - } - - // Bottom line is rendered by the prompt -} - -void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message) -{ - Message_Append(Server, MSG_TYPE_STANDARD, Dest, Src, Message); - //printf("<%s:%s:%s> %s\n", Server->Name, Dest, Src, Message); -} - -void ParseServerLine_Numeric(tServer *Server, const char *ident, int Num, char *Line) -{ - int pos = 0; - const char *message; - const char *user = GetValue(Line, &pos); - - if( Line[pos] == ':' ) { - message = Line + pos + 1; - } - else { - message = GetValue(Line, &pos); - } - - switch(Num) - { - case 332: // Topic - user = message; // Channel - message = Line + pos + 1; // Topic - Message_AppendF(Server, MSG_TYPE_SERVER, user, user, "Topic: %s", message); - break; - case 333: // Topic set by - user = message; // Channel - message = GetValue(Line, &pos); // User - GetValue(Line, &pos); // Timestamp - Message_AppendF(Server, MSG_TYPE_SERVER, user, user, "Set by %s", message); - break; - case 353: // /NAMES list - // = :list - // '=' was eaten in and set to message - user = GetValue(Line, &pos); // Actually channel - message = Line + pos + 1; // List - Message_AppendF(Server, MSG_TYPE_SERVER, user, user, "Names: %s", message); - break; - case 366: // end of /NAMES list - // :msg - user = message; - message = Line + pos + 1; - Message_Append(Server, MSG_TYPE_SERVER, user, user, message); - break; - case 372: // MOTD Data - case 375: // MOTD Start - case 376: // MOTD End - - default: - //printf("[%s] %i %s\n", Server->Name, num, message); - Message_Append(Server, MSG_TYPE_SERVER, ident, user, message); - break; - } -} - -void ParseServerLine_String(tServer *Server, const char *ident, const char *cmd, char *Line) -{ - int pos = 0; - _SysDebug("ident=%s,cmd=%s,Line=%s", ident, cmd, Line); - if( strcmp(cmd, "NOTICE") == 0 ) - { - const char *class = GetValue(Line, &pos); - _SysDebug("NOTICE class='%s'", class); - - const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos); - - //printf("[%s] NOTICE %s: %s\n", Server->Name, ident, message); - char *ident_bang = strchr(ident, '!'); - if( ident_bang ) { - *ident_bang = '\0'; - } - Message_Append(Server, MSG_TYPE_NOTICE, ident, "", message); - } - else if( strcmp(cmd, "PRIVMSG") == 0 ) - { - const char *dest = GetValue(Line, &pos); - const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos); - - // TODO: Catch when the privmsg is addressed to the user - -// Cmd_PRIVMSG(Server, dest, ident, message); - char *ident_bang = strchr(ident, '!'); - if( ident_bang ) { - *ident_bang = '\0'; - } - Message_Append(Server, MSG_TYPE_STANDARD, ident, dest, message); - } - else if( strcmp(cmd, "JOIN" ) == 0 ) - { - const char *channel = Line + pos + 1; - - Message_AppendF(Server, MSG_TYPE_JOIN, "", channel, "%s has joined", ident); - //Window_Create(Server, channel); - } - else if( strcmp(cmd, "PART" ) == 0 ) - { - const char *channel = Line + pos + 1; - - Message_AppendF(Server, MSG_TYPE_PART, "", channel, "%s has left", ident); - //Window_Create(Server, channel); - } - else - { - Message_AppendF(Server, MSG_TYPE_SERVER, "", "", "Unknown message %s (%s)", cmd, Line); - } -} - -/** - */ -void ParseServerLine(tServer *Server, char *Line) -{ - int pos = 0; - - _SysDebug("[%s] %s", Server->Name, Line); - - // Message? - if( *Line == ':' ) - { - pos ++; - const char *ident = GetValue(Line, &pos); // Ident (user or server) - const char *cmd = GetValue(Line, &pos); - - // Numeric command - if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) ) - { - int num; - num = (cmd[0] - '0') * 100; - num += (cmd[1] - '0') * 10; - num += (cmd[2] - '0') * 1; - - ParseServerLine_Numeric(Server, ident, num, Line+pos); - } - else - { - ParseServerLine_String(Server, ident, cmd, Line+pos); - } - } - else { - const char *cmd = GetValue(Line, &pos); - - if( strcmp(cmd, "PING") == 0 ) { - writef(Server->FD, "PONG %s\n", Line+pos); - } - else { - // Command to client - Message_AppendF(NULL, MSG_TYPE_UNK, "", "", "Client Command: %s", Line); - } - } -} - -/** - * \brief Process incoming lines from the server - */ -int ProcessIncoming(tServer *Server) -{ - char *ptr, *newline; - int len; - - // While there is data in the buffer, read it into user memory and - // process it line by line - // ioctl#8 on a TCP client gets the number of bytes in the recieve buffer - // - Used to avoid blocking - #if NON_BLOCK_READ - while( (len = _SysIOCtl(Server->FD, 8, NULL)) > 0 ) - { - #endif - // Read data - len = _SysRead(Server->FD, &Server->InBuf[Server->ReadPos], BUFSIZ - Server->ReadPos); - if( len == -1 ) { - return -1; - } - Server->InBuf[Server->ReadPos + len] = '\0'; - - // Break into lines - ptr = Server->InBuf; - while( (newline = strchr(ptr, '\n')) ) - { - *newline = '\0'; - if( newline[-1] == '\r' ) newline[-1] = '\0'; - ParseServerLine(Server, ptr); - ptr = newline + 1; - } - - // Handle incomplete lines - if( ptr - Server->InBuf < len + Server->ReadPos ) { - // Update the read position - // InBuf ReadPos ptr ReadPos+len - // | old | new used | new unused | - Server->ReadPos = len + Server->ReadPos - (ptr - Server->InBuf); - // Copy stuff back (moving "new unused" to the start of the buffer) - memcpy(Server->InBuf, ptr, Server->ReadPos); - } - else { - Server->ReadPos = 0; - } - #if NON_BLOCK_READ - } - #endif - - return 0; + Windows_RepaintCurrent(); } /** @@ -863,12 +218,3 @@ char *GetValue(char *Src, int *Ofs) return ret; } -void SetCursorPos(int Row, int Col) -{ - printf("\x1B[%i;%iH", Row, Col); -} - -static inline int isdigit(int ch) -{ - return '0' <= ch && ch < '9'; -} diff --git a/Usermode/Applications/irc_src/message.h b/Usermode/Applications/irc_src/message.h new file mode 100644 index 00000000..0b86f124 --- /dev/null +++ b/Usermode/Applications/irc_src/message.h @@ -0,0 +1,37 @@ +/* + */ +#ifndef _MESSAGE_H_ +#define _MESSAGE_H_ + +enum eMessageTypes +{ + MSG_TYPE_NULL, + MSG_TYPE_SERVER, // Server message + + MSG_TYPE_NOTICE, // NOTICE command + MSG_TYPE_JOIN, // JOIN command + MSG_TYPE_PART, // PART command + MSG_TYPE_QUIT, // QUIT command + + MSG_TYPE_STANDARD, // Standard line + MSG_TYPE_ACTION, // /me + + MSG_TYPE_UNK +}; + +enum eMessageClass +{ + MSG_CLASS_BARE, // source is unused, just gets timestamped + MSG_CLASS_CLIENT, // source optional, prefixed by '-!-' + MSG_CLASS_WALL, // [SOURCE] MSG -- Server-provided message + MSG_CLASS_MESSAGE, // MSG + MSG_CLASS_ACTION, // * SOURCE MSG +}; + +typedef struct sMessage tMessage; + +//extern tMessage *Message_AppendF(tServer *Server, int Type, const char *Src, const char *Dst, const char *Fmt, ...) __attribute__((format(__printf__,5,6))); +//extern tMessage *Message_Append(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message); + +#endif + diff --git a/Usermode/Applications/irc_src/pseudo_curses.c b/Usermode/Applications/irc_src/pseudo_curses.c new file mode 100644 index 00000000..bdcb3cf9 --- /dev/null +++ b/Usermode/Applications/irc_src/pseudo_curses.c @@ -0,0 +1,30 @@ +/* + */ +#include "pseudo_curses.h" +#include +#include +#include + + int giTerminal_Width = 80; + int giTerminal_Height = 25; + +void ACurses_Init(void) +{ + if( _SysIOCtl(1, DRV_IOCTL_TYPE, NULL) != DRV_TYPE_TERMINAL ) { + _SysDebug("note: assuming 80x25, can't get terminal dimensions"); + giTerminal_Width = 80; + giTerminal_Height = 25; + } + else { + struct ptydims dims; + _SysIOCtl(1, PTY_IOCTL_GETDIMS, &dims); + giTerminal_Width = dims.W; + giTerminal_Height = dims.H; + } +} + +void SetCursorPos(int Row, int Col) +{ + printf("\x1B[%i;%iH", Row, Col); +} + diff --git a/Usermode/Applications/irc_src/pseudo_curses.h b/Usermode/Applications/irc_src/pseudo_curses.h new file mode 100644 index 00000000..0548e923 --- /dev/null +++ b/Usermode/Applications/irc_src/pseudo_curses.h @@ -0,0 +1,13 @@ +/* + */ +#ifndef _ACURSES_H_ +#define _ACURSES_H_ + +extern int giTerminal_Width; +extern int giTerminal_Height; + +extern void ACurses_Init(void); +extern void SetCursorPos(int Row, int Col); + +#endif + diff --git a/Usermode/Applications/irc_src/server.c b/Usermode/Applications/irc_src/server.c new file mode 100644 index 00000000..c8bfea58 --- /dev/null +++ b/Usermode/Applications/irc_src/server.c @@ -0,0 +1,340 @@ +/* + */ +#include +#include +#include "server.h" +#include "window.h" +#include +#include // isdigit +#include // vsnprintf + +// === PROTOTYPES === +tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber); + int Server_HandleIncoming(tServer *Server); +void ParseServerLine(tServer *Server, char *Line); + +// === GLOBALS === +const char *gsUsername = "user"; +const char *gsHostname = "acess"; +const char *gsRealName = "Acess2 IRC Client"; +const char *gsNickname = "acess"; +tServer *gpServers; + +// === CODE === +void Servers_FillSelect(int *nfds, fd_set *rfds, fd_set *efds) +{ + for( tServer *srv = gpServers; srv; srv = srv->Next ) + { + FD_SET(srv->FD, rfds); + FD_SET(srv->FD, efds); + if( srv->FD >= *nfds ) + *nfds = srv->FD+1; + } +} + +void Servers_HandleSelect(int nfds, const fd_set *rfds, const fd_set *efds) +{ + for( tServer *srv = gpServers; srv; srv = srv->Next ) + { + if(FD_ISSET(srv->FD, rfds)) + { + int rv = Server_HandleIncoming(srv); + if(rv) + { + // Oops, error + _SysDebug("ProcessIncoming failed on FD%i (Server %p %s)", + srv->FD, srv, srv->Name); + Exit("Processing error"); + } + } + + if(FD_ISSET(srv->FD, efds)) + { + _SysDebug("Error on FD%i (Server %p %s)", + srv->FD, srv, srv->Name); + Exit("Socket error"); + } + } +} + +void Servers_CloseAll(const char *QuitMessage) +{ + while( gpServers ) + { + tServer *srv = gpServers; + gpServers = srv->Next; + + Server_SendCommand(srv, "QUIT :%s", QuitMessage); + _SysClose(srv->FD); + free(srv); + } +} + +/** + * \brief Connect to a server + */ +tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber) +{ + tServer *ret; + + ret = calloc(1, sizeof(tServer) + strlen(Name) + 1); + + strcpy(ret->Name, Name); + + // Connect to the remove server + ret->FD = OpenTCP( AddressString, PortNumber ); + if( ret->FD == -1 ) { + free(ret); + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket"); + return NULL; + } + + ret->Nick = strdup(gsNickname); + + // Append to open list + ret->Next = gpServers; + gpServers = ret; + + // Read some initial data + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket"); + Server_HandleIncoming(ret); + + // Identify + Server_SendCommand(ret, "USER %s %s %s : %s", gsUsername, gsHostname, AddressString, gsRealName); + Server_SendCommand(ret, "NICK %s", ret->Nick); + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Identified"); + + return ret; +} + +const char *Server_GetNick(const tServer *Server) +{ + return Server->Nick; +} + +void Server_SendCommand(tServer *Server, const char *Format, ...) +{ + va_list args; + int len; + + va_start(args, Format); + len = vsnprintf(NULL, 0, Format, args); + va_end(args); + + char buf[len+1]; + va_start(args, Format); + vsnprintf(buf, len+1, Format, args); + va_end(args); + + _SysWrite(Server->FD, buf, len); + _SysWrite(Server->FD, "\n", 1); +} + +void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message) +{ + tWindow *win; + if( strcmp(Dest, Server->Nick) == 0 ) { + win = Windows_GetByName(Server, Src); + if(!win) + win = Window_Create(Server, Src); + } + else { + win = Windows_GetByName(Server, Dest); + if(!win) + win = Window_Create(Server, Dest); + } + Window_AppendMessage(win, MSG_CLASS_MESSAGE, Src, "%s", Message); +} + +/** + * \brief Process incoming lines from the server + */ +int Server_HandleIncoming(tServer *Server) +{ + char *ptr, *newline; + int len; + + // While there is data in the buffer, read it into user memory and + // process it line by line + // ioctl#8 on a TCP client gets the number of bytes in the recieve buffer + // - Used to avoid blocking + #if NON_BLOCK_READ + while( (len = _SysIOCtl(Server->FD, 8, NULL)) > 0 ) + { + #endif + // Read data + len = _SysRead(Server->FD, &Server->InBuf[Server->ReadPos], sizeof(Server->InBuf) - Server->ReadPos); + if( len == -1 ) { + return -1; + } + Server->InBuf[Server->ReadPos + len] = '\0'; + + // Break into lines + ptr = Server->InBuf; + while( (newline = strchr(ptr, '\n')) ) + { + *newline = '\0'; + if( newline[-1] == '\r' ) newline[-1] = '\0'; + ParseServerLine(Server, ptr); + ptr = newline + 1; + } + + // Handle incomplete lines + if( ptr - Server->InBuf < len + Server->ReadPos ) { + // Update the read position + // InBuf ReadPos ptr ReadPos+len + // | old | new used | new unused | + Server->ReadPos = len + Server->ReadPos - (ptr - Server->InBuf); + // Copy stuff back (moving "new unused" to the start of the buffer) + memcpy(Server->InBuf, ptr, Server->ReadPos); + } + else { + Server->ReadPos = 0; + } + #if NON_BLOCK_READ + } + #endif + + return 0; +} + +void ParseServerLine_Numeric(tServer *Server, const char *ident, int Num, char *Line) +{ + int pos = 0; + const char *message; + const char *user = GetValue(Line, &pos); + const char *timestamp; + + if( Line[pos] == ':' ) { + message = Line + pos + 1; + } + else { + message = GetValue(Line, &pos); + } + + switch(Num) + { + case 332: // Topic + user = message; // Channel + message = Line + pos + 1; // Topic + Window_AppendMsg_Topic( Windows_GetByNameOrCreate(Server, user), message ); + break; + case 333: // Topic set by + user = message; // Channel + message = GetValue(Line, &pos); // User + timestamp = GetValue(Line, &pos); // Timestamp + Window_AppendMsg_TopicTime( Windows_GetByNameOrCreate(Server, user), message, timestamp ); + break; + case 353: // /NAMES list + // = :list + // '=' was eaten in and set to message + user = GetValue(Line, &pos); // Actually channel + message = Line + pos + 1; // List + // TODO: parse and store + Window_AppendMessage( Windows_GetByNameOrCreate(Server, user), MSG_CLASS_CLIENT, "NAMES", message ); + break; + case 366: // end of /NAMES list + // :msg + // - Ignored + break; + case 372: // MOTD Data + case 375: // MOTD Start + case 376: // MOTD End + + default: + //printf("[%s] %i %s\n", Server->Name, num, message); + Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "Unknown %i %s", Num, message); + break; + } +} + +void ParseServerLine_String(tServer *Server, const char *ident, const char *cmd, char *Line) +{ + int pos = 0; + _SysDebug("ident=%s,cmd=%s,Line=%s", ident, cmd, Line); + if( strcmp(cmd, "NOTICE") == 0 ) + { + const char *class = GetValue(Line, &pos); + _SysDebug("NOTICE class='%s'", class); + + const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos); + + //printf("[%s] NOTICE %s: %s\n", Server->Name, ident, message); + char *ident_bang = strchr(ident, '!'); + if( ident_bang ) { + *ident_bang = '\0'; + } + // TODO: Colour codes + Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "%s %s", ident, message); + } + else if( strcmp(cmd, "PRIVMSG") == 0 ) + { + const char *dest = GetValue(Line, &pos); + const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos); + + char *ident_bang = strchr(ident, '!'); + if( ident_bang ) { + *ident_bang = '\0'; + } + Cmd_PRIVMSG(Server, dest, ident, message); + } + else if( strcmp(cmd, "JOIN" ) == 0 ) + { + const char *channel = Line + pos + 1; + + Window_AppendMsg_Join( Windows_GetByNameOrCreate(Server, channel), ident ); + } + else if( strcmp(cmd, "PART" ) == 0 ) + { + const char *channel = Line + pos + 1; + + Window_AppendMsg_Part( Windows_GetByNameOrCreate(Server, channel), ident, "" ); + } + else + { + Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_BARE, Server->Name, "Unknown command '%s' %s", cmd, Line); + } +} + +/** + */ +void ParseServerLine(tServer *Server, char *Line) +{ + int pos = 0; + + _SysDebug("[%s] %s", Server->Name, Line); + + // Message? + if( *Line == ':' ) + { + pos ++; + const char *ident = GetValue(Line, &pos); // Ident (user or server) + const char *cmd = GetValue(Line, &pos); + + // Numeric command + if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) ) + { + int num; + num = (cmd[0] - '0') * 100; + num += (cmd[1] - '0') * 10; + num += (cmd[2] - '0') * 1; + + ParseServerLine_Numeric(Server, ident, num, Line+pos); + } + else + { + ParseServerLine_String(Server, ident, cmd, Line+pos); + } + } + else { + const char *cmd = GetValue(Line, &pos); + + if( strcmp(cmd, "PING") == 0 ) { + Server_SendCommand(Server, "PONG %s", Line+pos); + } + else { + // Command to client + Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "UNK Client Command: %s", Line); + } + } +} diff --git a/Usermode/Applications/irc_src/server.h b/Usermode/Applications/irc_src/server.h new file mode 100644 index 00000000..0fd4f515 --- /dev/null +++ b/Usermode/Applications/irc_src/server.h @@ -0,0 +1,29 @@ +/* + */ +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include "common.h" + +typedef struct sServer { + struct sServer *Next; + int FD; + char InBuf[1024+1]; + int ReadPos; + char *Nick; + char Name[]; +} tServer; + +extern void Servers_FillSelect(int *nfds, fd_set *rfds, fd_set *efds); +extern void Servers_HandleSelect(int nfds, const fd_set *rfds, const fd_set *efds); +extern void Servers_CloseAll(const char *QuitMessage); + +extern tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber); +extern int Server_HandleIncoming(tServer *Server); + +extern const char *Server_GetNick(const tServer *Server); + +extern void Server_SendCommand(tServer *Server, const char *Format, ...) __attribute__((format(__printf__,2,3))); + +#endif + diff --git a/Usermode/Applications/irc_src/window.c b/Usermode/Applications/irc_src/window.c new file mode 100644 index 00000000..1314cec3 --- /dev/null +++ b/Usermode/Applications/irc_src/window.c @@ -0,0 +1,243 @@ +/* + */ +#include "window.h" +#include +#include +#include +#include // TODO: replace with calls into ACurses_* +#include +#include + +struct sMessage +{ + struct sMessage *Next; + time_t Timestamp; + enum eMessageClass Class; + char *Source; // Pointer to the end of `Data` + char Data[]; +}; + +struct sWindow +{ + struct sWindow *Next; + tMessage *Messages; + tServer *Server; //!< Canonical server (can be NULL) + int ActivityLevel; + char Name[]; // Channel name / remote user +}; + +// === PROTOTYPES === +void Windows_RepaintCurrent(void); + int Windows_int_PaintMessage(tMessage *Message); + +// === GLOBALS === +tWindow gWindow_Status = { + NULL, NULL, NULL, // No next, empty list, no server + 0, {""} // No activity, empty name (rendered as status) +}; +tWindow *gpWindows = &gWindow_Status; +tWindow *gpCurrentWindow = &gWindow_Status; + +// === CODE === +void Windows_RepaintCurrent(void) +{ + tMessage *msg = gpCurrentWindow->Messages; + + // TODO: Title bar? + + // Note: This renders from the bottom up + for( int y = giTerminal_Height - 1; y -- && msg; msg = msg->Next) + { + y -= Windows_int_PaintMessage(msg); + } + + // Bottom line is rendered by the prompt + +} + +int Windows_int_PaintMessage(tMessage *Message) +{ + printf("\33[T"); // Scroll down 1 (free space below) + SetCursorPos(giTerminal_Height-2, 1); + + size_t prefixlen = 0; + prefixlen += printf("%02i:%02i:%02i ", (Message->Timestamp/3600)%24, (Message->Timestamp/60)%60, Message->Timestamp%60); + switch(Message->Class) + { + case MSG_CLASS_BARE: break; + case MSG_CLASS_CLIENT: + if(Message->Source) + prefixlen += printf("[%s] ", Message->Source); + prefixlen += printf("-!- "); + break; + case MSG_CLASS_WALL: + prefixlen += printf("[%s] ", Message->Source); + break; + case MSG_CLASS_MESSAGE: + prefixlen += printf("<%s> ", Message->Source); + break; + case MSG_CLASS_ACTION: + prefixlen += printf("* %s ", Message->Source); + break; + } + int avail = giTerminal_Width - prefixlen; + int msglen = strlen(Message->Data); + + int nLines = 1; + printf("%.*s", avail, Message); + while( msglen > avail ) { + msglen -= avail; + Message += avail; + printf("\33[T"); + SetCursorPos(giTerminal_Height-2, prefixlen+1); + printf("%.*s", avail, Message); + nLines ++; + } + + return nLines; +} + +void Windows_SwitchTo(tWindow *Window) +{ + gpCurrentWindow = Window; + Redraw_Screen(); +} + +void Windows_SetStatusServer(tServer *Server) +{ + gWindow_Status.Server = Server; +} + +tWindow *Windows_GetByIndex(int Index) +{ + tWindow *win; + for( win = gpWindows; win && Index--; win = win->Next ) + ; + return win; +} + +tWindow *Windows_GetByNameEx(tServer *Server, const char *Name, bool CreateAllowed) +{ + tWindow *ret, *prev = NULL; + int num = 1; + + // Get the end of the list and check for duplicates + // TODO: Cache this instead + for( ret = &gWindow_Status; ret; prev = ret, ret = ret->Next ) + { + if( ret->Server == Server && strcmp(ret->Name, Name) == 0 ) + { + return ret; + } + num ++; + } + if( !CreateAllowed ) { + return NULL; + } + + ret = malloc(sizeof(tWindow) + strlen(Name) + 1); + ret->Messages = NULL; + ret->Server = Server; + ret->ActivityLevel = 1; + strcpy(ret->Name, Name); + + if( prev ) { + ret->Next = prev->Next; + prev->Next = ret; + } + else { // Shouldn't happen really + ret->Next = gpWindows; + gpWindows = ret; + } + +// printf("Win %i %s:%s created\n", num, Server->Name, Name); + + return ret; +} + +tWindow *Windows_GetByName(tServer *Server, const char *Name) +{ + return Windows_GetByNameEx(Server, Name, false); +} + +tWindow *Window_Create(tServer *Server, const char *Name) +{ + return Windows_GetByNameEx(Server, Name, true); +} + + +tWindow *Window_int_ParseSpecial(const tWindow *Window) +{ + if( Window == NULL ) + return gpCurrentWindow; + if( Window == WINDOW_STATUS ) + return &gWindow_Status; + return (tWindow*)Window; +} + +const char *Window_GetName(const tWindow *Window) { + return Window_int_ParseSpecial(Window)->Name; +} +tServer *Window_GetServer(const tWindow *Window) { + return Window_int_ParseSpecial(Window)->Server; +} +bool Window_IsChat(const tWindow *Window) { + return Window_int_ParseSpecial(Window) != &gWindow_Status; +} + +// ----------------------------- +// Messages +// ----------------------------- +void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...) +{ + Window = Window_int_ParseSpecial(Window); + + va_list args; + + va_start(args, Message); + size_t len = vsnprintf(NULL, 0, Message, args); + va_end(args); + + tMessage *msg = malloc( sizeof(tMessage) + len+1 + (Source?strlen(Source)+1:0) ); + assert(msg); + + msg->Class = Class; + msg->Source = (Source ? msg->Data + len+1 : NULL); + va_start(args, Message); + vsnprintf(msg->Data, len+1, Message, args); + va_end(args); + + msg->Next = Window->Messages; + Window->Messages = msg; + + if( Window == gpCurrentWindow ) + { + // Scroll if needed, and redraw? + // - Lazy option of draw at bottom of screen + printf("\33[s"); // Save cursor + Windows_int_PaintMessage(msg); + printf("\x1b[u"); // Restore cursor + } +} + +void Window_AppendMsg_Join(tWindow *Window, const char *Usermask) +{ + Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined %s", Usermask, Window->Name); +} +void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason) +{ + Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined quit (%s)", Usermask, Reason); +} +void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason) +{ + Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined left %s (%s)", Usermask, Window->Name, Reason); +} +void Window_AppendMsg_Topic(tWindow *Window, const char *Topic) +{ + Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic of %s is %s", Window->Name, Topic); +} +void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestamp) +{ + Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic set by %s at %s", User, Timestamp); +} + diff --git a/Usermode/Applications/irc_src/window.h b/Usermode/Applications/irc_src/window.h new file mode 100644 index 00000000..da97f3e4 --- /dev/null +++ b/Usermode/Applications/irc_src/window.h @@ -0,0 +1,36 @@ +/* + */ +#ifndef _WINDOW_H_ +#define _WINDOW_H_ + +#include "common.h" +#include "message.h" +#include + +typedef struct sWindow tWindow; +extern void Windows_RepaintCurrent(void); + +extern void Windows_SetStatusServer(tServer *Server); +extern tWindow *Window_Create(tServer *Server, const char *Name); +extern tWindow *Windows_GetByIndex(int Index); +extern tWindow *Windows_GetByName(tServer *Server, const char *Name); +static inline tWindow *Windows_GetByNameOrCreate(tServer *Server, const char *Name) { + return Window_Create(Server, Name); +} +extern void Windows_SwitchTo(tWindow *Window); + +extern void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...) + __attribute__((format(__printf__,4,5))); +extern void Window_AppendMsg_Join(tWindow *Window, const char *Usermask); +extern void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason); +extern void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason); +extern void Window_AppendMsg_Topic(tWindow *Window, const char *Topic); +extern void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestmap); + +extern const char *Window_GetName(const tWindow *Window); +extern tServer *Window_GetServer(const tWindow *Window); +extern bool Window_IsChat(const tWindow *Window); + +#define WINDOW_STATUS ((void*)-1) + +#endif -- 2.20.1