diff options
author | xengineering <me@xengineering.eu> | 2022-08-14 13:33:56 +0200 |
---|---|---|
committer | xengineering <me@xengineering.eu> | 2022-08-14 13:33:56 +0200 |
commit | 15229dbf21535abe8b4c68cf4b0bab370f06f48f (patch) | |
tree | c3e894db170831fc781e1b8322f6cb1d5c314bd4 | |
download | limox-15229dbf21535abe8b4c68cf4b0bab370f06f48f.tar limox-15229dbf21535abe8b4c68cf4b0bab370f06f48f.tar.zst limox-15229dbf21535abe8b4c68cf4b0bab370f06f48f.zip |
First public version
-rw-r--r-- | README.txt | 68 | ||||
-rw-r--r-- | gui.c | 267 | ||||
-rw-r--r-- | gui.h | 3 | ||||
-rw-r--r-- | main.c | 80 | ||||
-rw-r--r-- | meson.build | 4 |
5 files changed, 422 insertions, 0 deletions
diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..5efd598 --- /dev/null +++ b/README.txt @@ -0,0 +1,68 @@ + + +----- +LimoX +----- + +The Linux on mobile XMPP (LimoX) client. It should be fast, compliant and +usable on mobile linux devices like smartphones or tablets. + + +Roadmap +------- + +- [x] build a GTK 4 GUI +- [ ] finish minimal viable product (MVP) to send and receive text messages + - [x] static GUI widgets + - [x] dynamic GUI widgets (like text messages) + - [ ] roster request + - [ ] receiving one-to-one text messages + - [ ] sending one-to-one text messages +- [ ] persistence with sqlite3 +- [ ] refactoring +- [ ] XMPP core compliance suite 2022 / client [1] + - [ ] RFC 6120: XMPP Core + - [ ] RFC 7590: TLS for XMPP + - [ ] XEP-0030: Service Discovery + - [ ] XEP-0115: Entity Capabilities +- [ ] XMPP IM compliance suite 2022 / client [1] + - [ ] RFC 6121: XMPP Instant Messaging + - [ ] XEP-0245: The /me Command + - [ ] XEP-0054: vcard-temp + - [ ] XEP-0280: Message Carbons + - [ ] XEP-0045: Multi-User Chat + - [ ] XEP-0249: Direct MUC Invitations + - [ ] XEP-0363: HTTP File Upload +- [ ] XMPP mobile compliance suite 2022 / client [1] + - [ ] XEP-0198: Stream Management + - [ ] XEP-0352: Client State Indication +- [ ] implement further XEPs + - [ ] XEP-0077: In-Band Registration (just password change) + - [ ] XEP-0313: Message Archive Management + - [ ] XEP-0333: Chat Markers + - [ ] XEP-0085: Chat State Notifications + - [ ] XEP-0191: Blocking Command + - [ ] XEP-0308: Last Message Correction + - [ ] XEP-0393: Message Styling + - [ ] XEP-0444: Message Reactions + - [ ] XEP-0392: Consistent Color Generation + - [ ] XEP-0286: Mobile Considerations on LTE Networks + - [ ] XEP-0184: Message Delivery Receipts + - [ ] XEP-0380: Explicit Message Encryption + - [ ] XEP-0384: OMEMO Encryption + + +Building +-------- + +Just run these commands to build and run: + + meson build + ninja -C build + ./build/limox + +Run `rm -r build` to clean the build folder. You can get a help page by running +`./build/limox -h`. + + +[1] https://xmpp.org/extensions/xep-0459.html @@ -0,0 +1,267 @@ + + +#include <time.h> +#include <gtk/gtk.h> +#include <stdio.h> + + +struct chat { + GtkWidget* roster_item; // the button in the roster list + GtkWidget* chat_layout_box; // the layout box of the chat page + GtkWidget* chat_content_box; // the content box of the chat page + GtkWidget* text_entry; // where new messages are typed + struct chat* next; // entry to support the linked list +}; + + +// linked list of chats +static struct chat* chats = NULL; + +// 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 + printf("Connecting with:\nJID: %s\nPWD: %s\n", jid_text, pwd_text); +} + +static void disconnect_cb(void) { + + gtk_stack_set_visible_child(GTK_STACK(stack), connector_box); + + // just dummy output + printf("Disconnected!\n"); +} + +static void to_chat(GtkWidget* layout_box) { + gtk_stack_set_visible_child(GTK_STACK(stack), layout_box); +} + +static void to_roster(void) { + gtk_stack_set_visible_child(GTK_STACK(stack), roster_layout_box); +} + +void send_message(struct chat* chat) { + + // get recipient and message text + const char* recipient = gtk_button_get_label(GTK_BUTTON(chat->roster_item)); + const char* text = gtk_editable_get_text(GTK_EDITABLE(chat->text_entry)); + + // execute dummy XMPP send TODO + printf("Sending to %s:\n> %s\n", recipient, text); + + // add message content to the chat + GtkWidget* message = gtk_label_new(text); + gtk_box_append(GTK_BOX(chat->chat_content_box), message); + + // clear text input + GtkEntryBuffer* empty_buffer = gtk_entry_buffer_new("", 0); + gtk_entry_set_buffer(GTK_ENTRY(chat->text_entry), empty_buffer); +} + +void add_chat(char* jid) { + + // create chat struct and initialize + struct chat* chat = malloc(sizeof(struct chat)); + chat->roster_item = gtk_button_new_with_label(jid); + chat->chat_layout_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); + chat->chat_content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + chat->text_entry = gtk_entry_new(); + chat->next = NULL; + + // add chat page to stack + gtk_stack_add_child(GTK_STACK(stack), chat->chat_layout_box); + + // add a button to go back to roster + GtkWidget* back = gtk_button_new_with_label("back"); + gtk_box_append(GTK_BOX(chat->chat_layout_box), back); + g_signal_connect_swapped(back, "clicked", G_CALLBACK(to_roster), NULL); + + // add a scrolled window to chat page + GtkWidget* scrolled = gtk_scrolled_window_new(); + gtk_widget_set_vexpand(scrolled, TRUE); + gtk_box_append(GTK_BOX(chat->chat_layout_box), scrolled); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), + chat->chat_content_box); + + // add text entry and send button to chat page + gtk_box_append(GTK_BOX(chat->chat_layout_box), chat->text_entry); + GtkWidget* send_button = gtk_button_new_with_label("send"); + gtk_box_append(GTK_BOX(chat->chat_layout_box), send_button); + g_signal_connect_swapped(send_button, "clicked", G_CALLBACK(send_message), + chat); + + // add chat to roster list + gtk_box_append(GTK_BOX(roster_content_box), chat->roster_item); + g_signal_connect_swapped(chat->roster_item, "clicked", G_CALLBACK(to_chat), + chat->chat_layout_box); + + // append chat to linked list of chats + struct chat* current = chats; + while (true) { + if (current == NULL) { + chats = chat; + break; + } else if (current->next == NULL) { + current->next = chat; + break; + } else { + current = current->next; + } + } + +} + +void add_incoming_text_message(char* sender_jid, char* content) { + + // do not add message if list of chats is empty + if (chats == NULL) { + return; + } + + // find chat with corresponding JID + struct chat* chat = chats; + while (true) { + const char* jid = gtk_button_get_label(GTK_BUTTON(chat->roster_item)); + if (strcmp(jid, sender_jid) == 0) { + break; // this seems to be the correct chat + } else if (chat->next == NULL) { + return; // no matching chat found - cannot add message + } else { + chat = chat->next; // go to next chat in linked list + } + } + + // add given message content to the chat + GtkWidget* message = gtk_label_new(content); + gtk_box_append(GTK_BOX(chat->chat_content_box), message); + +} + +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); + + // 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); + + // TODO remove this + // add dummy roster items + for (int i=0; i<10; i++) { + // char buffer[50]; + char* buffer = malloc(50*sizeof(char)); + sprintf(buffer, "contact-%d@example.com", i+1); + add_chat(buffer); + } + + // TODO just for debugging + for (int i=0; i<20; i++) { + add_incoming_text_message("contact-2@example.com", "Just a test."); + // message = gtk_label_new("Test message"); + // gtk_box_append(GTK_BOX(chat->chat_content_box), message); + } + +} + +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 delay(unsigned long int micros) { + + unsigned long int now = clock(); + while ((clock() - now) < micros) {} + +} + +static void idle_cb(void) { + + // just a dummy workload + delay(1000); + +} + +void run_gui(void) { + +#ifdef GTK_SRCDIR + g_chdir(GTK_SRCDIR); +#endif + + app = gtk_application_new("eu.xengineering.limox", G_APPLICATION_FLAGS_NONE); + 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); + +} @@ -0,0 +1,3 @@ +void run_gui(void); +void add_chat(char* jid); +void add_incoming_text_message(char* sender_jid, char* content); @@ -0,0 +1,80 @@ + + +#include <stdbool.h> +#include <stdio.h> +#include <unistd.h> + +#include "gui.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; + bool help; +} 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 { + run_gui(); + } + +} + +// getOpts parses all command line arguments. +static void get_opts(Options* opts, int argc, char* argv[]) { + + // initialize Options struct + opts->unknown = false; + opts->help = false; + + // parse all arguments to Options struct + int c; + while ((c=getopt(argc, argv, "h")) != -1) { + + switch (c) { + case 'h': + opts->help = true; + break; + default: + opts->unknown = true; + } + + } + +} + +// printHelp prints out the help page. +static void print_help(void) { + + fprintf(stderr, + "The Linux on mobile XMPP (LimoX) client.\n" + "\n" + "Usage: limox [-h]\n" + "\n" + "Options:\n" + " -h print help page\n" + ); + +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a743460 --- /dev/null +++ b/meson.build @@ -0,0 +1,4 @@ +project('LimoX', 'c') +gtkdep = dependency('gtk4') +strophedep = dependency('libstrophe') +executable('limox', ['main.c', 'gui.c'], dependencies : [gtkdep, strophedep]) |