From 2efc9022bf064136bb7cd25bd59971f2b419ff48 Mon Sep 17 00:00:00 2001 From: xengineering Date: Sun, 27 Nov 2022 18:53:12 +0100 Subject: Switch completely to SDL2 version The now legacy version of LimoX with GTK4 and libstrophe is now not part of the main branch anymore. There might be a legacy branch keeping this version at the time of reading. This cut of implemented functionality is motivated by these reasons: - Implementing XMPP is fun, educative and gives full control. - Low level graphics with SDL2 is portable, fast, educative an mature. - I do not have to use GLib and a crazy event loop anymore (run and hide) --- README.md | 50 ++--------- data.c | 149 --------------------------------- data.h | 40 --------- gtk.c | 272 ------------------------------------------------------------ gui.c | 65 +++++++++++++++ gui.h | 12 --- main.c | 50 ++++------- meson.build | 5 +- net.h | 14 ---- sdl2.c | 88 -------------------- strophe.c | 228 -------------------------------------------------- xmpp.c | 48 +++++++++-- xmpp.h | 1 + 13 files changed, 133 insertions(+), 889 deletions(-) delete mode 100644 data.c delete mode 100644 data.h delete mode 100644 gtk.c create mode 100644 gui.c delete mode 100644 net.h delete mode 100644 sdl2.c delete mode 100644 strophe.c create mode 100644 xmpp.h diff --git a/README.md b/README.md index a82b47d..ca58bbd 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,8 @@ The Linux on mobile XMPP (LimoX) client. It should be fast, compliant and usable on mobile linux devices like smartphones or tablets. Also mind the [project page][2] and my [packaging repository][3] for Arch Linux -with support for LimoX. - -Currently there is an ongoing rewrite in progress. There is the initial version -of LimoX and a rewrite (`limox_sdl2` in the build folder). The rewrite -substitutes GTK4 by SDL2 for graphics and maybe will also drop libstrophe as a -dependency. - -A nice reference which helps me a lot is [Beej's Guide to Network -Programming][4]. +with support for LimoX. A nice reference which helps me a lot is [Beej's Guide +to Network Programming][4]. ## Building and dependencies @@ -27,8 +20,6 @@ Install these build dependencies: Install these runtime dependencies: -- libstrophe -- gtk4 - sdl2 Just run these commands to build and run: @@ -37,7 +28,6 @@ Just run these commands to build and run: meson setup build ninja -C build ./build/limox -./build/limox_sdl2 # the SDL2 based rewrite ``` Run `rm -r build` to clean the build folder. You can get a help page by running @@ -46,37 +36,15 @@ Run `rm -r build` to clean the build folder. You can get a help page by running ## Roadmap -- [x] build a GTK 4 GUI -- [x] finish minimal viable product (MVP) to send and receive text messages - - [x] static GUI widgets - - [x] dynamic GUI widgets (like text messages) - - [x] roster request - - [x] receiving one-to-one text messages - - [x] sending one-to-one text messages -- [ ] switch from GTK4 to SDL2 - - [x] implement second executable with empty SDL2 window - - [ ] integrate libstrophe into SDL2 event loop (validate with presence) - - [ ] re-implement login page - - [ ] re-implement roster page - - [ ] re-implement chat page -- [ ] refactoring - - [x] fix reconnection bug - - [ ] check memory management - - [ ] handle roster updates - - [ ] remove states enum in net.c (doubles logic from libstrophe) - - [ ] consistent error handling (maybe including semantic error codes) - - [ ] logging / debug flags and output - - [ ] OS signal handling - - [ ] write docstrings - - [ ] check if code is splint-clean -- [ ] persistence with sqlite3 -- [ ] XEP-0077: In-Band Registration (just password change) -- [ ] XEP-0313: Message Archive Management +- [x] develop minimal viable product with GTK4 (branch `legacy/gtk4-libstrophe`) - [ ] implement XMPP core client ([compliance suite 2022][1]) - [ ] RFC 6120: XMPP Core - [ ] RFC 7590: TLS for XMPP - [ ] XEP-0030: Service Discovery - [ ] XEP-0115: Entity Capabilities +- [ ] implement XMPP mobile client ([compliance suite 2022][1]) + - [ ] XEP-0198: Stream Management + - [ ] XEP-0352: Client State Indication - [ ] implement XMPP IM client ([compliance suite 2022][1]) - [ ] RFC 6121: XMPP Instant Messaging - [ ] XEP-0245: The /me Command @@ -85,11 +53,9 @@ Run `rm -r build` to clean the build folder. You can get a help page by running - [ ] XEP-0045: Multi-User Chat - [ ] XEP-0249: Direct MUC Invitations - [ ] XEP-0363: HTTP File Upload -- [ ] implement XMPP mobile client ([compliance suite 2022][1]) - - [ ] XEP-0198: Stream Management - - [ ] XEP-0352: Client State Indication -- [ ] evaluate multi-platform support (up to Linux, Windows, Android, Mac, iOS) - [ ] implement further XEPs + - [ ] XEP-0077: In-Band Registration (just password change) + - [ ] XEP-0313: Message Archive Management - [ ] XEP-0286: Mobile Considerations on LTE Networks - [ ] XEP-0333: Chat Markers - [ ] XEP-0085: Chat State Notifications diff --git a/data.c b/data.c deleted file mode 100644 index f19075c..0000000 --- a/data.c +++ /dev/null @@ -1,149 +0,0 @@ - - -#include -#include -#include -#include - -#include "gui.h" -#include "data.h" - - -static roster_item_t* roster = NULL; - - -/* check if JID is already in roster data structure */ -bool is_jid_known(const char* jid) { - - roster_item_t* i; - - // iterate over all roster items - for (i = roster; i != NULL; i=i->next) { - if (strcmp(i->jid, jid) == 0) { - return true; - } - } - - return false; - -} - -void data_add_roster_item(const char* jid, const char* subscription, - const char* name) { - - // FIXME handle roster updates - if (is_jid_known(jid)) { - return; - } - - // allocate datastructures - roster_item_t* item = malloc(sizeof(roster_item_t)); - chat_t* chat = malloc(sizeof(chat_t)); - - // initialize chat - chat->messages = NULL; - chat->jid = malloc(sizeof(char)*strlen(jid)); - strcpy(chat->jid, jid); - gui_add_chat_widget(chat); - - // initialize roster item - if (name == NULL) { - item->name = NULL; - } else { - item->name = malloc(sizeof(char)*strlen(name)); - strcpy(item->name, name); - } - item->jid = malloc(sizeof(char)*strlen(jid)); - strcpy(item->jid, jid); - if (strcmp(subscription, "none") == 0) { - item->sub = SUB_NONE; - } else if (strcmp(subscription, "to") == 0) { - item->sub = SUB_TO; - } else if (strcmp(subscription, "from") == 0) { - item->sub = SUB_FROM; - } else if (strcmp(subscription, "both") == 0) { - item->sub = SUB_BOTH; - } else { - fprintf(stderr, "Invalid subscription '%s'!\n", subscription); - return; - } - item->chat = chat; - item->next = NULL; - gui_add_roster_item_widget(item); - - // add item to roster datastructure - if (roster == NULL) { - roster = item; - } else { - roster_item_t* current = roster; - while (current->next != NULL) // loop until end of linked list - current = current->next; - current->next = item; - } - -} - -void data_add_incoming_message(const char* sender_jid, const char* content) { - - // TODO rework based on chat->jid - // find correct chat - chat_t* chat = NULL; - roster_item_t* i; - int bare_len; // length of bare JID - for (i = roster; i != NULL; i=i->next) { - bare_len = strlen(i->jid); - if (strncmp(i->jid, sender_jid, bare_len) == 0) { - chat = i->chat; - break; - } - } - if (chat == NULL) { - fprintf(stderr, "Could not find chat for message from '%s'!\n", sender_jid); - return; - } - - // initialize message_t - message_t* msg = malloc(sizeof(message_t)); - msg->sender_jid = malloc(sizeof(char)*strlen(sender_jid)); - strcpy(msg->sender_jid, sender_jid); - msg->content = malloc(sizeof(char)*strlen(content)); - strcpy(msg->content, content); - msg->next = NULL; - gui_add_message_widget(msg, i->chat); - - // find pointer to next message of chat - message_t* m; - for (m = chat->messages; m != NULL; m=m->next); - - // append message to chat - m = msg; - - // TODO could be more correct to update chat->jid to full JID of just - // received message so that future responses go to this ressource - -} - -void data_add_outgoing_message(const char* sender_jid, const char* content, - chat_t* chat) { - - // allocate and initialize message_t - message_t* msg = malloc(sizeof(message_t)); - msg->sender_jid = malloc(sizeof(char)*strlen(sender_jid)); - strcpy(msg->sender_jid, sender_jid); - msg->recipient_jid = malloc(sizeof(char)*strlen(chat->jid)); - strcpy(msg->recipient_jid, chat->jid); - msg->content = malloc(sizeof(char)*strlen(content)); - strcpy(msg->content, content); - msg->next = NULL; - - // find pointer to next message of chat and add this message - message_t* m; - for (m = chat->messages; m != NULL; m=m->next); - m = msg; - - // TODO send this message via XMPP - - // create GUI widget for this message - gui_add_message_widget(msg, chat); - -} diff --git a/data.h b/data.h deleted file mode 100644 index 68a4b9b..0000000 --- a/data.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef DATA_H -#define DATA_H - -// https://www.rfc-editor.org/rfc/rfc6121#section-2.1.2.5 -typedef enum { - SUB_NONE, - SUB_TO, - SUB_FROM, - SUB_BOTH -} subscription_t; - -typedef struct _message_t { - char* content; - char* sender_jid; - char* recipient_jid; - struct _message_t* next; - void* widget; -} message_t; - -typedef struct _chat_t { - char* jid; - message_t* messages; - void* widget; -} chat_t; - -typedef struct _roster_item_t { - char* name; // could be NULL - char* jid; - subscription_t sub; - chat_t* chat; - struct _roster_item_t* next; - void* widget; -} roster_item_t; - -void data_add_roster_item(const char* jid, const char* subscription, - const char* name); -void data_add_incoming_message(const char* sender_jid, const char* content); -void data_add_outgoing_message(const char* sender_jid, const char* content, - chat_t* chat); -#endif diff --git a/gtk.c b/gtk.c deleted file mode 100644 index af24a73..0000000 --- a/gtk.c +++ /dev/null @@ -1,272 +0,0 @@ - - -#include -#include - -#include "net.h" -#include "data.h" - - -typedef struct _chat_widget_t { - GtkWidget* page; - GtkWidget* content; - GtkWidget* entry; -} chat_widget_t; - - -// the GTK application is available as global variable -static GtkApplication* app; - -// all GTK widgets are accessible in this file via global variables -static GtkWidget* window; -static GtkWidget* stack; - -// connector page -static GtkStackPage* connector_page; -static GtkWidget* connector_box; -static GtkWidget* connector_jid_label; -static GtkWidget* connector_jid_entry; -static GtkWidget* connector_pwd_label; -static GtkWidget* connector_pwd_entry; -static GtkWidget* connector_button; - -// roster page -static GtkStackPage* roster_page; -static GtkWidget* roster_layout_box; -static GtkWidget* roster_scrolled; -static GtkWidget* roster_content_box; -static GtkWidget* roster_button; - - -static void quit_cb(void) { - g_application_quit(G_APPLICATION(app)); -} - -static void connect_cb(void) { - - const char* jid_text = gtk_editable_get_text(GTK_EDITABLE(connector_jid_entry)); - const char* pwd_text = gtk_editable_get_text(GTK_EDITABLE(connector_pwd_entry)); - - gtk_stack_set_visible_child(GTK_STACK(stack), roster_layout_box); - - // just dummy output - fprintf(stderr, "Connecting with:\nJID: %s\nPWD: %s\n", jid_text, pwd_text); - - net_connect(jid_text, pwd_text); -} - -static void disconnect_cb(void) { - - gtk_stack_set_visible_child(GTK_STACK(stack), connector_box); - - // just dummy output - fprintf(stderr, "Disconnected!\n"); - - net_disconnect(); -} - -void send_message(chat_t* chat) { - - // cast void* to GUI-specific chat_widget_t* - chat_widget_t* chat_widget = (chat_widget_t*)chat->widget; - - // get text of message and own JID - const char* text = gtk_editable_get_text(GTK_EDITABLE(chat_widget->entry)); - const char* self = gtk_editable_get_text(GTK_EDITABLE(connector_jid_entry)); - - // add message to datastructure and send it - data_add_outgoing_message(self, text, chat); - net_send_message(self, text, chat->jid); - - // clear text input - GtkEntryBuffer* buffer; - buffer = gtk_entry_get_buffer(GTK_ENTRY(chat_widget->entry)); - gtk_entry_buffer_delete_text(buffer, 0, -1); -} - -/* Set credentials from environment variables if given */ -static void set_debug_credentials(GtkWidget* user, GtkWidget* pwd) { - - char* user_str = getenv("LIMOX_USER"); - if (user_str != NULL) { - GtkEntryBuffer* buffer; - buffer = gtk_entry_get_buffer(GTK_ENTRY(user)); - gtk_entry_buffer_set_text(buffer, user_str, -1); - } - - char* pwd_str = getenv("LIMOX_PWD"); - if (pwd_str != NULL) { - gtk_editable_set_text(GTK_EDITABLE(pwd), pwd_str); - } - -} - -static void build_static_widgets(void) { - - // main window with stack - window = gtk_window_new(); - gtk_window_set_title(GTK_WINDOW(window), "LimoX"); - gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); - stack = gtk_stack_new(); - gtk_window_set_child(GTK_WINDOW(window), stack); - - // connector page - connector_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); - connector_page = gtk_stack_add_child(GTK_STACK(stack), connector_box); - connector_jid_label = gtk_label_new("XMPP address (JID)"); - gtk_box_append(GTK_BOX(connector_box), connector_jid_label); - connector_jid_entry = gtk_entry_new(); - gtk_box_append(GTK_BOX(connector_box), connector_jid_entry); - connector_pwd_label = gtk_label_new("Password"); - gtk_box_append(GTK_BOX(connector_box), connector_pwd_label); - connector_pwd_entry = gtk_password_entry_new(); - gtk_box_append(GTK_BOX(connector_box), connector_pwd_entry); - connector_button = gtk_button_new_with_label("Connect"); - gtk_box_append(GTK_BOX(connector_box), connector_button); - set_debug_credentials(connector_jid_entry, connector_pwd_entry); - - // roster page - roster_layout_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); - roster_page = gtk_stack_add_child(GTK_STACK(stack), roster_layout_box); - roster_button = gtk_button_new_with_label("Disconnect"); - gtk_box_append(GTK_BOX(roster_layout_box), roster_button); - roster_scrolled = gtk_scrolled_window_new(); - gtk_widget_set_vexpand(roster_scrolled, TRUE); - roster_content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(roster_scrolled), - roster_content_box); - gtk_box_append(GTK_BOX(roster_layout_box), roster_scrolled); - -} - -static void activate(void) { - - build_static_widgets(); - - // configure widgets - gtk_window_set_application(GTK_WINDOW(window), app); - g_signal_connect_swapped(window, "close-request", G_CALLBACK(quit_cb), app); - g_signal_connect_swapped( - connector_button, "clicked", G_CALLBACK(connect_cb), NULL); - g_signal_connect_swapped( - roster_button, "clicked", G_CALLBACK(disconnect_cb), NULL); - - gtk_widget_show(window); -} - -static void idle_cb(void) { - - net_run_once(); - -} - -void gui_run(void) { - -#ifdef GTK_SRCDIR - g_chdir(GTK_SRCDIR); -#endif - - app = gtk_application_new("eu.xengineering.limox", G_APPLICATION_DEFAULT_FLAGS); - g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); - g_idle_add(G_SOURCE_FUNC(idle_cb), NULL); - - g_application_run(G_APPLICATION(app), 0, NULL); - g_object_unref(app); - -} - -static void to_roster(void) { - - gtk_stack_set_visible_child(GTK_STACK(stack), roster_layout_box); - -} - -void gui_add_chat_widget(chat_t* chat) { - - // allocate chat_widget_t and save as void* in given chat_t - chat_widget_t* chat_widget = malloc(sizeof(chat_widget_t)); - chat->widget = (void*)chat_widget; - - // create chat page (GtkBox) and add it to the stack - GtkWidget* chat_layout_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); - gtk_stack_add_child(GTK_STACK(stack), chat_layout_box); - chat_widget->page = chat_layout_box; - - // create and add back button - GtkWidget* back = gtk_button_new_with_label("back"); - gtk_box_append(GTK_BOX(chat_layout_box), back); - g_signal_connect_swapped(back, "clicked", G_CALLBACK(to_roster), NULL); - - // create and add scrolled window - GtkWidget* scrolled = gtk_scrolled_window_new(); - gtk_widget_set_vexpand(scrolled, TRUE); - gtk_box_append(GTK_BOX(chat_layout_box), scrolled); - - // create and add content box for dynamic message widgets - GtkWidget* chat_content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), - chat_content_box); - chat_widget->content = chat_content_box; - - // create and add text input field - GtkWidget* text_entry = gtk_entry_new(); - gtk_box_append(GTK_BOX(chat_layout_box), text_entry); - chat_widget->entry = text_entry; - - // create and add send button - GtkWidget* send_button = gtk_button_new_with_label("send"); - gtk_box_append(GTK_BOX(chat_layout_box), send_button); - g_signal_connect_swapped(send_button, "clicked", G_CALLBACK(send_message), - chat); - -} - -void gui_add_message_widget(message_t* message, chat_t* chat) { - - const char* connector = "\n"; - char* label_content = malloc(sizeof(char)*(strlen(message->sender_jid) - + strlen(connector) - + strlen(message->content))); - sprintf(label_content, "%s%s%s", message->sender_jid, connector, - message->content); - GtkWidget* label = gtk_label_new(label_content); - gtk_label_set_wrap(GTK_LABEL(label), TRUE); - gtk_label_set_selectable(GTK_LABEL(label), TRUE); - message->widget = (void*)label; - chat_widget_t* chat_widget = (chat_widget_t*)chat->widget; - gtk_box_append(GTK_BOX(chat_widget->content), label); - free(label_content); - -} - -static void to_chat(chat_t* chat) { - - chat_widget_t* chat_widget = (chat_widget_t*)chat->widget; - gtk_stack_set_visible_child(GTK_STACK(stack), chat_widget->page); - -} - -void gui_add_roster_item_widget(roster_item_t* item) { - - // print debug message - if (item->name) { - fprintf(stderr, "roster item: %s, %s, sub:%d\n", item->name, item->jid, - item->sub); - } else { - fprintf(stderr, "roster item: (no name), %s, sub:%d\n", item->jid, item->sub); - } - - // create widget for roster item - GtkWidget* roster_item_widget; - if (item->name == NULL || strcmp(item->name, "") == 0) { - roster_item_widget = gtk_button_new_with_label(item->jid); - } else { - roster_item_widget = gtk_button_new_with_label(item->name); - } - - // add roster item widget to roster page - gtk_box_append(GTK_BOX(roster_content_box), roster_item_widget); - g_signal_connect_swapped(roster_item_widget, "clicked", G_CALLBACK(to_chat), - item->chat); - -} diff --git a/gui.c b/gui.c new file mode 100644 index 0000000..04a53b3 --- /dev/null +++ b/gui.c @@ -0,0 +1,65 @@ + + +/* mention the SDL2 documentation at http://wiki.libsdl.org/APIByCategory */ + + +#include +#include + + +void gui_run(void) { + + bool quit = false; + SDL_Event event; + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; + uint32_t* pixels; + + // init SDL2 and create window + SDL_Init(SDL_INIT_VIDEO); + window = SDL_CreateWindow("LimoX", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, + 0 + ); + + // output video driver to stderr + fprintf(stderr, "SDL2 video driver: %s\n", SDL_GetCurrentVideoDriver()); + + // create and initialize renderer, texture and pixel buffer + renderer = SDL_CreateRenderer(window, -1, 0); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STATIC, 640, 480); + pixels = malloc(sizeof(uint32_t) * 640 * 480); + memset(pixels, 255, 640 * 480 * sizeof(uint32_t)); + + // handle failed window creation + if (window == NULL) { + fprintf(stderr, "Failed to create SDL2 window!\n"); + return; + } else { + while (!quit) { + SDL_UpdateTexture(texture, NULL, pixels, 640 * sizeof(uint32_t)); + SDL_WaitEvent(&event); + switch (event.type) + { + case SDL_QUIT: + quit = true; + break; + } + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + SDL_DestroyWindow(window); + } + + free(pixels); + + // TODO this seems to end in memory access errors but ... why? + //SDL_DestroyTexture(texture); + //SDL_DestroyRenderer(renderer); + + SDL_Quit(); + +} diff --git a/gui.h b/gui.h index d4a91f4..24d4878 100644 --- a/gui.h +++ b/gui.h @@ -1,13 +1 @@ -#ifndef GUI_H -#define GUI_H - -#include "data.h" - void gui_run(void); -void gui_connected(char* jid, char* password); -void gui_disconnected(void); -void gui_add_roster_item_widget(roster_item_t* item); -void gui_add_chat_widget(chat_t* chat); -void gui_add_message_widget(message_t* message, chat_t* chat); - -#endif diff --git a/main.c b/main.c index 016ea99..b86e911 100644 --- a/main.c +++ b/main.c @@ -5,17 +5,9 @@ #include #include "gui.h" -#include "net.h" +#include "xmpp.h" -// error code definition -typedef enum error { - Ok = 0, - UnknownError = 1, - WrongArgs = 2, - GuiError = 3, -} Error; - // configuration struct for all command line options typedef struct options { bool unknown; @@ -23,28 +15,6 @@ typedef struct options { } Options; -// prototypes -static void get_opts(Options* opts, int argc, char* argv[]); -static void print_help(void); - - -int main(int argc, char* argv[]) { - - // parse command line options - Options opts; - get_opts(&opts, argc, argv); - - // handle correct use case - if (opts.unknown || opts.help) { - print_help(); - } else { - net_init(); - gui_run(); - net_quit(); - } - -} - // parses all command line arguments. static void get_opts(Options* opts, int argc, char* argv[]) { @@ -63,9 +33,7 @@ static void get_opts(Options* opts, int argc, char* argv[]) { default: opts->unknown = true; } - } - } // prints out the help page. @@ -81,3 +49,19 @@ static void print_help(void) { ); } + +int main(int argc, char* argv[]) { + + // parse command line options + Options opts; + get_opts(&opts, argc, argv); + + // handle correct use case + if (opts.unknown || opts.help) { + print_help(); + } else { + xmpp_connect(); + gui_run(); + } + +} diff --git a/meson.build b/meson.build index 8a21907..a675aa6 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,3 @@ project('LimoX', 'c') -gtkdep = dependency('gtk4') -strophedep = dependency('libstrophe') sdl2dep = dependency('sdl2') -executable('limox', ['main.c', 'gtk.c', 'strophe.c', 'data.c'], dependencies : [gtkdep, strophedep]) -executable('limox_sdl2', ['main.c', 'sdl2.c', 'xmpp.c', 'data.c'], dependencies : [sdl2dep]) +executable('limox', ['main.c', 'gui.c', 'xmpp.c'], dependencies : [sdl2dep]) diff --git a/net.h b/net.h deleted file mode 100644 index 3bd184a..0000000 --- a/net.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef NET_H -#define NET_H - -// life cycle / event loop related functions -void net_init(void); // initialize net -void net_run_once(void); // process event loop for a short amount of time -void net_quit(void); // disconnect and clean up - -// interface for the GUI implementation -void net_connect(const char* jid, const char* password); -void net_disconnect(void); -void net_send_message(const char* sender, const char* content, - const char* recipient); -#endif diff --git a/sdl2.c b/sdl2.c deleted file mode 100644 index e034446..0000000 --- a/sdl2.c +++ /dev/null @@ -1,88 +0,0 @@ - - -/* mention the SDL2 documentation at http://wiki.libsdl.org/APIByCategory */ - - -#include -#include - -#include "net.h" -#include "data.h" - - -void gui_run(void) { - - bool quit = false; - SDL_Event event; - SDL_Window* window; - SDL_Renderer* renderer; - SDL_Texture* texture; - uint32_t* pixels; - - // init SDL2 and create window - SDL_Init(SDL_INIT_VIDEO); - window = SDL_CreateWindow("LimoX", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, - 0 - ); - - // output video driver to stderr - fprintf(stderr, "SDL2 video driver: %s\n", SDL_GetCurrentVideoDriver()); - - // create and initialize renderer, texture and pixel buffer - renderer = SDL_CreateRenderer(window, -1, 0); - texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STATIC, 640, 480); - pixels = malloc(sizeof(uint32_t) * 640 * 480); - memset(pixels, 255, 640 * 480 * sizeof(uint32_t)); - - // handle failed window creation - if (window == NULL) { - fprintf(stderr, "Failed to create SDL2 window!\n"); - return; - } else { - while (!quit) { - SDL_UpdateTexture(texture, NULL, pixels, 640 * sizeof(uint32_t)); - SDL_WaitEvent(&event); - switch (event.type) - { - case SDL_QUIT: - quit = true; - break; - } - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - } - SDL_DestroyWindow(window); - } - - free(pixels); - - // TODO this seems to end in memory access errors but ... why? - //SDL_DestroyTexture(texture); - //SDL_DestroyRenderer(renderer); - - SDL_Quit(); - -} - -void gui_connected(char* jid, char* password) { - -} - -void gui_disconnected(void) { - -} - -void gui_add_roster_item_widget(roster_item_t* item) { - -} - -void gui_add_chat_widget(chat_t* chat) { - -} - -void gui_add_message_widget(message_t* message, chat_t* chat) { - -} diff --git a/strophe.c b/strophe.c deleted file mode 100644 index c037bc1..0000000 --- a/strophe.c +++ /dev/null @@ -1,228 +0,0 @@ - - -#include -#include -#include -#include -#include - -#include - -#include "gui.h" -#include "net.h" -#include "data.h" - - -// the state of net -typedef enum { - DISCONNECTED, // initial state - CONNECTING, // connection was requested but is not yet established - CONNECTED, // there is an active XMPP connetion to the server - SUSPENDED // not connected, but there is an open XMPP stream to be - // reconnected (see XEP-0198 for details) -} net_state_t; - - -// these variables stay initialized for the whole runtime -static net_state_t state; -static xmpp_ctx_t* ctx; - -// these variables stay initialized while the application is not disconnected -static xmpp_conn_t* conn; -static long flags; - - -static int message_handler(xmpp_conn_t* conn, xmpp_stanza_t* stanza, - void* userdata) { - - xmpp_stanza_t* body = xmpp_stanza_get_child_by_name(stanza, "body"); - if (body == NULL) { - fprintf(stderr, "DEBUG: Got message stanza of type char without body!\n"); - return 1; - } - - const char* content = xmpp_stanza_get_text(body); - const char* sender = xmpp_stanza_get_from(stanza); - - data_add_incoming_message(sender, content); - - return 1; -} - -static int roster_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, - void *userdata) { - - // iterate over roster result - xmpp_stanza_t* query = xmpp_stanza_get_child_by_name(stanza, "query"); - for (xmpp_stanza_t* item = xmpp_stanza_get_children(query); item; - item = xmpp_stanza_get_next(item)) { - - data_add_roster_item(xmpp_stanza_get_attribute(item, "jid"), - xmpp_stanza_get_attribute(item, "subscription"), - xmpp_stanza_get_attribute(item, "name")); - } - - return 1; -} - -static void conn_handler(xmpp_conn_t* conn, xmpp_conn_event_t status,int error, - xmpp_stream_error_t* stream_error, void* userdata) { - - // FIXME handle error and stream_error (this could be a major bug) - // this implements at least debugging: - if (error != 0) { - fprintf(stderr, "conn_handler got error=%d\n", error); - } - if (stream_error != NULL) { - fprintf(stderr, "conn_handler got stream_error of type %d\n", - stream_error->type); - } - - switch (status) { - - case XMPP_CONN_CONNECT: - fprintf(stderr, "DEBUG: Got XMPP_CONN_CONNECT\n"); - - // add handler for stanzas - xmpp_handler_add(conn, message_handler, NULL, "message", "chat", - NULL); - - // add handler for roster response - xmpp_handler_add(conn, roster_handler, "jabber:iq:roster", "iq", - "result", NULL); - - // send initial presence - xmpp_stanza_t* presence; - presence = xmpp_presence_new(ctx); - xmpp_send(conn, presence); - xmpp_stanza_release(presence); - - // send roster request - xmpp_stanza_t* iq = xmpp_iq_new(ctx, "get", "initial_roster"); - xmpp_stanza_t* query = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(query, "query"); - xmpp_stanza_set_ns(query, XMPP_NS_ROSTER); - xmpp_stanza_add_child(iq, query); - xmpp_stanza_release(query); - xmpp_send(conn, iq); - xmpp_stanza_release(iq); - - break; - - case XMPP_CONN_RAW_CONNECT: - fprintf(stderr, "DEBUG: Got XMPP_CONN_RAW_CONNECT\n"); - break; - - case XMPP_CONN_DISCONNECT: - fprintf(stderr, "DEBUG: Got XMPP_CONN_DISCONNECT\n"); - break; - - case XMPP_CONN_FAIL: - fprintf(stderr, "DEBUG: Got XMPP_CONN_FAIL\n"); - break; - - default: - fprintf(stderr, "DEBUG: Got unknown connection status '%d'!\n", status); - exit(1); - } -} - -void net_init(void) { - - fprintf(stderr, "net_init()\n"); - - xmpp_initialize(); - ctx = xmpp_ctx_new(NULL, xmpp_get_default_logger(XMPP_LEVEL_DEBUG)); - - state = DISCONNECTED; - -} - -static void delay(unsigned long int micros) { - - unsigned long int now = clock(); - while ((clock() - now) < micros) {} - -} - -void net_run_once(void) { - - if (state == CONNECTING) { - if (xmpp_connect_client(conn, NULL, 0, conn_handler, NULL) - == XMPP_EOK) { - state = CONNECTED; - } else { - net_disconnect(); - } - } - - if (state != DISCONNECTED) { - xmpp_run_once(ctx, 200); - } - - // TODO This delay is useless. But without it this function gets only - // called once by GTK. This could be a GTK bug. - delay(10000); - -} - -void net_quit(void) { - - fprintf(stderr, "net_quit()\n"); - - net_disconnect(); - xmpp_shutdown(); - -} - -void net_connect(const char* jid, const char* password) { - - fprintf(stderr, "net_connect()\n"); - - conn = xmpp_conn_new(ctx); - - flags = 0; - flags |= XMPP_CONN_FLAG_MANDATORY_TLS; - xmpp_conn_set_flags(conn, flags); - - xmpp_conn_set_jid(conn, jid); - xmpp_conn_set_pass(conn, password); - - state = CONNECTING; - -} - -void net_disconnect(void) { - - fprintf(stderr, "net_disconnect()\n"); - - if (conn != NULL && xmpp_conn_is_connected(conn)) { - xmpp_disconnect(conn); - while (xmpp_conn_is_connected(conn)) { - xmpp_run_once(ctx, 200); // TODO avoid with additional - // state "disconnecting" - } - if (!xmpp_conn_release(conn)) { - fprintf(stderr, "DEBUG: Could not free connection!\n"); - } - } - - flags = 0; - state = DISCONNECTED; - -} - -void net_send_message(const char* sender, const char* content, - const char* recipient) { - - if (xmpp_conn_is_connected(conn)) { - char* uuid = xmpp_uuid_gen(ctx); - xmpp_stanza_t* msg = xmpp_message_new(ctx, "chat", recipient, uuid); - xmpp_stanza_set_from(msg, sender); - xmpp_message_set_body(msg, content); - xmpp_send(conn, msg); - } else { - fprintf(stderr, "net_send_message() called while not connected\n"); - } - -} diff --git a/xmpp.c b/xmpp.c index 865b235..e1ab985 100644 --- a/xmpp.c +++ b/xmpp.c @@ -30,14 +30,51 @@ char *domainpart(char *jid) } } - char* retval = (char *)malloc((stop-start+1) * sizeof(char)); - memcpy(retval, jid+start, (stop-start)*sizeof(char)); + char *retval = (char *)malloc((stop-start+1) * sizeof(char)); + memcpy(retval, jid+start, (stop-start) * sizeof(char)); retval[stop] = '\0'; return retval; } -void net_init(void) +/* + * Return the preferred struct addrinfo * or NULL + * + * This handles DNS resolution. It returns the first valid addrinfo which is + * returned by getaddrinfo. Mind that the addrinfo could use IPv6 instead of + * IPv4. + */ +struct addrinfo *get_addrinfo(char *domain) +{ + struct addrinfo hints; + struct addrinfo *servinfo; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(domain, "xmpp-client", &hints, &servinfo) != 0) { + printf("Failed to resolve hostname '%s'.\n", domain); + return NULL; + } + struct addrinfo *p; + for(p=servinfo; p!=NULL; p=p->ai_next) { + if (p->ai_family == AF_INET) { + printf("an IPv4!\n"); // TODO + + } else if (p->ai_family == AF_INET6) { + printf("an IPv6!\n"); // TODO + } else { + printf("Unknown addrinfo address type.\n"); + } + } + return NULL; +} + +/* + * Initialize the network connection to the XMPP server + * + * TODO: Error handling is missing. + */ +void xmpp_connect(void) { printf("net_init()\n"); @@ -46,9 +83,6 @@ void net_init(void) char *domain = domainpart(user_str); printf("Trying to connect as '%s' with '%s'.\n", user_str, pwd_str); printf("Domainpart is '%s'.\n", domain); -} -void net_quit(void) -{ - printf("net_quit()\n"); + get_addrinfo(domain); } diff --git a/xmpp.h b/xmpp.h new file mode 100644 index 0000000..b07e3b9 --- /dev/null +++ b/xmpp.h @@ -0,0 +1 @@ +void xmpp_connect(void); -- cgit v1.2.3-70-g09d2