#include #include #include #include #include unsigned char dc_ui_def_u[] = { #include }; char * dc_ui_def = (char *) dc_ui_def_u; #define DC_UI_SET_STATUS(b, s) gtk_label_set_text(GTK_LABEL(gtk_builder_get_object(b, "dc_main_status")), s) struct dc_ui_data { GtkBuilder * b; GKeyFile * k; }; enum dc_permissions { /* other permissions exist, but are not implemented/understood */ DC_ALL_PERMISSIONS = 1 << 3, /* this is incredibly retarded, why is this SEPARATE?!? - admins */ DC_CHANNEL_VIEW = 1 << 10, /* all enum fields here have values same as the server values */ DC_MESSAGE_SEND = 1 << 11, DC_MESSAGE_READ = 1 << 16, /* na tistem vegova serverju sem lahko pošiljal ne pa bral sporočil */ DC_VOICE_LISTEN = 1 << 20, DC_VOICE_SPEAK = 1 << 21 }; enum dc_channel_type { /* other types exist, but are not implemented/understood */ DC_TEXT, /* all enum fields here have values same as the values that the server sends */ DC_DM, DC_VOICE, DC_DM2 /* retarded. server sometimes sends this... converted to DC_DM at parsing server resp. */ }; struct dc_program { /* parent struct of dc_client, in case multi-login will ever be implemented (no) */ struct dc_client * clients; /* yesfree */ /* dc_program contains the storage of all */ size_t clients_sizeof; /* structs in program. freeing is done from */ struct dc_guild * guilds; /* yesfree */ /* here and clasification (chans of a guild) */ size_t guilds_sizeof; /* is done via the use of linked lists. */ struct dc_channel * channels; /* yesfree */ /* before a network query, this storage may be */ size_t channels_sizeof; /* used to check if we already have the info */ struct dc_message * messages; /* yesfree */ /* already. for example to get dc_user from */ size_t messages_sizeof; /* user id-user may be available on another */ struct dc_role * roles; /* yesfree */ /* guild. sizeof=length so make sure heap */ size_t roles_sizeof; /* *alloc()ations are fast. they are on linux */ }; /* http://ž.ga/linuxfast */ struct dc_user { unsigned long long int id; short int discriminator; char * username; /* yesfree */ }; struct dc_role { unsigned long long int id; char * name; /* yesfree */ enum dc_permissions permissions; struct dc_guild * guild; /* nofree - owner of the role */ struct dc_role * next; /* nofree - next role (linked list of all roles of dc_guild) */ }; struct dc_role_membership { struct dc_guild * guild; /* nofree */ struct dc_user * user; /* nofree */ struct dc_role * role; /* nofree */ }; struct dc_client { char * authorization; /* yesfree - authorization header value */ char * email; /* yesfree */ char * password; /* yesfree */ struct dc_user * user; /* nofree - logged in user */ struct dc_guild * guild; /* nofree - first guild */ }; struct dc_guild { char * name; /* yesfree */ unsigned long long int id; /* 0 for virtual DMs guild */ struct dc_client * client; /* nofree */ char * alternative_messages_url; /* yesfree, internal - alternative messages url - for virtual DMs guild */ struct dc_guild * next; /* nofree - next guild (linked list of all guilds of dc_client) */ struct dc_channel * channel; /* nofree - first channel */ struct dc_role * role; /* nofree - first role. NOTE: first role is always role with role ID that is same as guild ID and it is the @everyone role */ enum dc_permissions permissions; }; struct dc_permission { /* permissions can be individual on a per-channel basis */ struct dc_permission * next; /* nofree - next permission (linked list of all perms of channel) */ enum dc_permissions allow; enum dc_permissions deny; unsigned long long int id; /* to whom does this permission apply */ struct dc_channel * channel; /* nofree - on which channel does it apply */ struct dc_user user; /* non-null if permission applies to a user */ struct dc_role role; /* non-null if it applies to a role */ int type; /* 0=role, 1=member NOTE: user and role may not be filled at start, check id in case */ }; /* permissions are only useful for checking OUR permissions, not others'. keep that in mind. */ struct dc_channel { char * name; /* yesfree - name */ char * topic; /* yesfree - topic */ unsigned long long int id; enum dc_channel_type type; struct dc_guild * guild; /* nofree */ struct dc_channel * next; /* nofree - next channel (linked list of all guilds of dc_guild) */ struct dc_message * message; /* nofree - first message (ordered by time) */ }; struct dc_message { struct dc_channel * channel; /* nofree */ struct dc_user * user; /* nofree */ char * message; /* yesfree */ char * attachment; /* yesfree */ time_t time; unsigned long long int id; struct dc_message * next; /* next message (linked list of all messages of dc_channel) */ }; /* # configuration file - loaded at startup, saved at exit, comments persist - description: [discord.c] multiline = true|false login = string password = string */ void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m to clear messages */ size_t i = 0; GtkWidget * b; GtkWidget * w, * w2; #define DC_USMTL 32 char t[DC_USMTL]; GtkGrid * g = GTK_GRID(gtk_builder_get_object(d->b, "dc_main_messages")); if (!m) { while (gtk_grid_get_child_at(g, 0, 0)) gtk_grid_remove_row(g, 0); return; } while ((w = gtk_grid_get_child_at(g, 0, i))) { /* now we get the index BEFORE which message will be placed */ struct dc_message * before, * after; before = (struct dc_message *) g_object_get_data(G_OBJECT(w), "message"); /* this literally mustn't and can't be NULL */ if ((w2 = gtk_grid_get_child_at(g, 0, i+1))) after = (struct dc_message *) g_object_get_data(G_OBJECT(w2), "message"); /* same here */ else { /* there is nothing after, message is new */ i++; /* BEFORE WHICH */ break; } if (m->time >= before->time && m->time <= after->time) { /* we've found a spot between two messages */ i++; /* SAME HERE. if there are no messages already, while will fail immediatley and i will remain 0 */ break; } } gtk_grid_insert_row(g, i); gtk_grid_insert_column(g, i); b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0 /* spacing pixels */); gtk_container_add(GTK_CONTAINER(b), gtk_label_new(m->user->username)); /* TODO: implement parsing markup here: bold, italic, underline; REMOVE < character; implement tags, timestamps, channels and spoilers with GTK ahrefs */ strftime(t, DC_USMTL, "%c", localtime(&m->time)); /* singlethreaded only */ gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t)); g_object_set_data(G_OBJECT(w), "message", m); gtk_grid_attach(g /* grid */, b /* widget to insert */, 0 /* left */, i /* top */, 1 /* width */, 1 /* height */); if (m->user == m->channel->guild->client->user) { /* TODO: if I posted the message, make it an editable textview */ } gtk_grid_attach(g, GTK_WIDGET(gtk_label_new(m->message)), 1, i, 1, 1); } gchar * gtk_text_buffer_get_all_text(GtkTextBuffer * b) { GtkTextIter s, e; gtk_text_buffer_get_start_iter(b, &s); gtk_text_buffer_get_end_iter(b, &e); gchar * c = gtk_text_iter_get_text(&s, &e); return c; /* must-g_free, transfer-full */ } G_MODULE_EXPORT void dc_ui_settings_ok (GtkButton * b, struct dc_ui_data * d) { g_key_file_set_boolean(d->k, "discord.c", "multiline", gtk_switch_get_active(GTK_SWITCH(gtk_builder_get_object(d->b, "dc_settings_multiline")))); g_key_file_set_string(d->k, "discord.c", "login", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_login")))); g_key_file_set_string(d->k, "discord.c", "password", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_password")))); gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b))); } G_MODULE_EXPORT void dc_ui_inputbox_changed (GtkWidget * i, struct dc_ui_data * d) { GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline")); GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline")); GtkWidget * b = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_send")); gchar * c = gtk_text_buffer_get_all_text(gtk_text_view_get_buffer(t)); gtk_widget_set_sensitive(b, c[0] || gtk_entry_get_text(e)[0] ? TRUE : FALSE); g_free(c); } void dc_ui_inputbox_activate (GtkWidget * a, struct dc_ui_data * d) { GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline")); GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline")); GtkTextBuffer * b = gtk_text_view_get_buffer(t); gchar * c = (gchar *) /* DROPPING const HERE!!! TODO: do this more politely with suppressions etc. */ gtk_entry_get_text(e); /* do not free this one */ if (c[0]) g_print("entry says: %s\n", c); else { /* we need text from the textview, entry is empty */ c = gtk_text_buffer_get_all_text(b); g_print("textview says: %s\n", c); a = NULL; /* so we mark the state, if we should free c or not */ } /* do stuff with c */ if (c[0] == '/') /* handle command */ switch (c[1]) { /* unlike before the rewrite / can't be escaped, because escaping / is useless. */ case 'c': /* clear messages, developing debugging TODO: delete before production useless */ case 'C': dc_ui_spawn_message(NULL, d); break; } else { /* send message to channel */ } /* stop doing stuff with c */ if (!a) g_free(c); gtk_text_buffer_set_text(b, "", -1); gtk_entry_set_text(e, ""); /* singleline */ } G_MODULE_EXPORT gboolean dc_ui_multiline_focus (GtkTextView * t, GtkDirectionType d /* pojma nimam, kako ta enum pove a mam fokus al ne... čudno */, gpointer u) { /* not working, there's not placeholder then */ char * p = "Enter message in this multiline text field or switch to a single line in preferences. Send message with Ctrl+Enter."; GtkTextBuffer * b = gtk_text_view_get_buffer(t); gchar * c = gtk_text_buffer_get_all_text(b); if (gtk_widget_has_focus(GTK_WIDGET(t))) { if (!strcmp(p, c)) gtk_text_buffer_set_text(b, "", -1); } else if (!c[0]) gtk_text_buffer_set_text(b, p, -1); g_free(c); return FALSE; /* to keep executing other handles for signals instead of finishing here. AFAIK, RTFM */ } G_MODULE_EXPORT void dc_ui_set_multiline (GtkSwitch * a, gboolean s, struct dc_ui_data * d) { GtkWidget * t = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_multiline")); GtkWidget * e = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_singleline")); gtk_widget_hide(e); gtk_widget_hide(t); if (s) gtk_widget_show(t); else gtk_widget_show(e); /* dc_ui_multiline_focus(GTK_TEXT_VIEW(t), 0, NULL); */ /* NOT WORKING, meh, there will be no placeholder */ /* just so we set the placeholder, the most important part, otherwise the user will not even see the textview on some themes <3 */ } G_MODULE_EXPORT void dc_ui_spawn_window (GtkToolButton * t, GtkWindow * w) { gtk_widget_show_all(GTK_WIDGET(w)); } G_MODULE_EXPORT gboolean dc_ui_handle_close (GtkButton * b, gpointer u) { gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b))); return TRUE; /* so that it stays non-deleted, main window sould call/be handled with gtk_main_quit */ } G_MODULE_EXPORT void dc_ui_reveal_password (GtkSwitch * t, gboolean s, GtkEntry * e) { gtk_entry_set_visibility(e, s); } void dc_ui_activate (GtkApplication * app, gpointer user_data) { GtkWidget * w; gchar * s; struct dc_ui_data d; d.b = gtk_builder_new_from_string(dc_ui_def, -1); w = GTK_WIDGET(gtk_builder_get_object(d.b, "dc_window_main")); gtk_builder_connect_signals(d.b, &d); /* začetek definicije dodatnih signalov */ /* g_signal_connect(gtk_builder_get_object(b, "dc_settings_multiline"), "state-set", G_CALLBACK(dc_ui_set_multiline), b); */ /* konec definicije dodatnih signalov */ #define dc_uacf "%s/%sdiscord.c", getenv("XDG_CONFIG_HOME") ? getenv("XDG_CONFIG_HOME") : getenv("HOME") ? getenv("HOME") : ".", getenv("XDG_CONFIG_HOME") ? "" : ".config/" /* as per XDG */ gchar fn[snprintf(NULL, 0, dc_uacf)]; sprintf(fn, dc_uacf); s = strrchr(fn, '/'); s[0] = '\0'; g_mkdir_with_parents(fn, 0700 /* as per XDG */); s[0] = '/'; d.k = g_key_file_new(); g_key_file_load_from_file(d.k, fn, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL); gtk_widget_show_all(w); dc_ui_set_multiline(NULL, g_key_file_get_boolean(d.k, "discord.c", "multiline", NULL), &d); dc_ui_inputbox_changed(NULL, &d); /* začetek aplikacije konfiguracijskih vrednosti v UI */ gtk_switch_set_state(GTK_SWITCH(gtk_builder_get_object(d.b, "dc_settings_multiline")), g_key_file_get_boolean(d.k, "discord.c", "multiline", NULL)); s = g_key_file_get_string(d.k, "discord.c", "login", NULL); gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d.b, "dc_settings_login")), s ? s : ""); g_free(s); s = g_key_file_get_string(d.k, "discord.c", "password", NULL); gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d.b, "dc_settings_password")), s ? s : ""); g_free(s); /* konec aplikacije konfiguracijskih vrednosti v UI */ gtk_main(); g_object_unref(d.b); if (!g_key_file_save_to_file(d.k, fn, NULL)) g_warning("couldn't save config"); g_key_file_free(d.k); } int dc_ui (int argc, char ** argv) { GtkApplication *app; int status; gtk_init(&argc, &argv); app = gtk_application_new("eu.sijanec.discord.c", G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(dc_ui_activate), NULL); status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; }