summaryrefslogtreecommitdiffstats
path: root/src/ui.c
blob: 79aa5242af5c60e997e13f55d63fcad7f7dce379 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#include <stdlib.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <string.h>
unsigned char dc_ui_def_u[] = {
#include <ui.xxd>
};
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;
}