summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2021-07-26 21:29:33 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2021-07-26 21:29:33 +0200
commit1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06 (patch)
treebf1297c93a6a69c40aebfafa5372d2b8ab97d24b
parentremoved useless dependency: math (diff)
downloaddiscord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar.gz
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar.bz2
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar.lz
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar.xz
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.tar.zst
discord.c-1cd23bc80c64d5e0e6c33e1d4f2b6cf1e2d53a06.zip
-rw-r--r--.gitignore10
-rw-r--r--Makefile19
-rw-r--r--README.md68
-rw-r--r--debian/control11
-rw-r--r--src/api.c771
-rw-r--r--src/i18n.h29
-rw-r--r--src/lib.c26
-rw-r--r--src/main.c83
-rw-r--r--src/ui.c635
-rw-r--r--src/ui.glade822
10 files changed, 1176 insertions, 1298 deletions
diff --git a/.gitignore b/.gitignore
index 74a1ce6..20a965e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
-lib/cJSON.h
-lib/cJSON.c
# the following is the binary
discord.c
# debugging log files
-netreq.log
-stderr.log
valgrind-out.txt
+# files I like to keep in my WD
+src.old/
+src/ui.glade~
+src/#ui.glade#
+# tmp
+tmp/
diff --git a/Makefile b/Makefile
index 092af8a..3ac796f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,29 @@
DESTDIR=/
+CFLAGS += -Wextra -Wall -pedantic -g -Itmp -Isrc -I. -odiscord.c -Wno-unused-parameter -export-dynamic -rdynamic -finput-charset=UTF-8 -fextended-identifiers
+CFLAGS += $(shell pkg-config --libs --cflags libwebsockets) $(shell pkg-config --libs --cflags gtk+-3.0) $(shell pkg-config --libs --cflags gmodule-export-2.0)
+CC=cc
+# notparallel ker morajo biti ukazi izvedeni po vrsti, mkdir se mora zgoditi pred xxd recimo.
+
+.NOTPARALLEL:
default:
- gcc -Wall -pedantic -g -Isrc -I. -pthread $$(ncursesw6-config --cflags --libs) src/main.c -lcjson -lcurl -lformw -odiscord.c
+ mkdir tmp -p
+ xxd -i < src/ui.glade > tmp/ui.xxd
+ echo ', 0' >> tmp/ui.xxd
+ $(CC) $(CFLAGS) src/main.c $(LIBS)
install:
mkdir -p $(DESTDIR)/usr/bin/
cp discord.c $(DESTDIR)/usr/bin/
distclean:
- rm discord.c -f
+ rm discord.c tmp -f
clean:
- rm discord.c -f
+ rm discord.c tmp -f
prepare:
- sudo apt install libncursesw6 libcurl4-openssl-dev libcjson-dev -y
+ sudo apt install build-essential libwebsockets-dev libcjson-dev libsoundio-dev libgtk-3-dev xxd -y
valgrind:
- valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt --suppressions=/usr/lib/valgrind/ncurses.supp --suppressions=misc/openssl.supp ./discord.c
+ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt --suppressions=misc/openssl.supp ./discord.c
diff --git a/README.md b/README.md
index d429f27..8812cb1 100644
--- a/README.md
+++ b/README.md
@@ -2,15 +2,7 @@
an alternative client for the discord messaging platform, written in the C programming language.
-## requirements
-
-* a POSIX system
-* GNU C library
-* GNU compiler collection
-* GNU Make
-* libcurl 7.17.0 or newer with HTTPS support
-* ncursesw6
-* libcjson-dev
+code rewrite in 0.0.4 brings a lot of new features and marks the 0.0.3 release obsolete, though it still kind of works for terminal users, although it is very bad. rewritten version is better because it does not reach ratelimits due to using a socket connection and not polling for messages via HTTP. it also has voice support and a GUI frontend.
## screenshots
@@ -27,20 +19,30 @@ sudo apt install discord.c
you need to add [my apt repository](https://prog.sijanec.eu/).
-built packages only exist for the `amd64` and `arm64`. for i386 grab the source package:
+built packages only exist for `amd64`, `arm64` and `i386`. for other architectures grab the source package:
```
-apt source discord.c
-# untar and follow install from source
+apt-get --install build discord.c
```
### caveman style install
```
make
-./discord.c -e email@address.example -p password
+./discord.c
```
+#### building requirements
+
+* a POSIX system - app is cross platform and should also be able to be compiled to all major OSes
+* `gcc`
+* GNU `make`
+* `libwebsockets-dev` for faster bidirectional connections to the server
+* `libgtk-3-dev` for the GUI
+* `libcjson-dev` for parsing server responses
+* `libsoundio-dev` for crossplatform sound output
+* `xxd` for embedding XML GtkBuilder UI definitions directly in the binary
+
## automatic building
automatic building is done on two servers, one for `arm64` and the other for `amd64`.
@@ -52,8 +54,44 @@ check the build badge before downloading:
* `amd64`: [https://cargova.sijanec.eu/prog/discord.c/](https://cargova.sijanec.eu/prog/discord.c/)
* `arm64`: [https://of.sijanec.eu/prog/discord.c/](https://of.sijanec.eu/prog/discord.c/)
-there deb packages and binaries.
+there are deb packages and binaries.
+
+## using discord.c as a library (libdiscord.c/discord.c-dev)
+
+discord.c is programmed in two separate sections, the ui section, that was previously made in ncurses, and the API section, that can, as of the code rewrite, be used independently in other applications. It is useful to write alternative frontends for accessing content available using discord.c by using the discord.c library, since that allows breaking API changes to be fixed in one place instead of fixing it for each individual program. There is no documentation provided for the library, but reading discord.c source code you will get the idea. Expect a Doxygen soon.
+
+to your surprise, you can use the ui section, as of the code rewrite, as a library as well and integrate other platforms into it. No docs yet here either.
+
+The package for Debian comes with include header files and compiled shared objects so it can be used as a dependency, but beware, this dependency will depend on GTK+3.0, which would be, in case you are doing a GUI with a different widget toolkit, useless.
+
+the program is, as of the code rewrite, single threaded, for increased stability and performance. it used to have separated network and ui threads, but now the threading model is abstracted with an event-based programming.
+
+## android port?
+
+it would be useful to have an android port, and luckily this is possible with little effort due to the GTK broadway backend that interfaces with a HTML renderer, WebView for example.
+
+<!--
+
+## working features
+
+* listing guilds and their channels, including private message channels
+* joining and listing past messages from those existing channels
+* sending messages into channels, ability to be joined into multiple channels at once
+* viewing attachments as links
+* joining voice channels, talking
## missing features
-* check for permissions before join
+* check for permissions before join (needs roles parsing)
+* tagging people, parsing tags of people/channels/emotes
+* ability to join video calls and stream video
+* altering profile
+* parsing per-channel nicks
+* creating channels and creating PMs
+* invites
+* refreshing channel and guild lists whilst begin turned on
+* responding to SIGWINCH and reloading ncurses windows whenever terminal window is resized
+* refreshing sound devices whilst being turned on
+* checking for rare system failures (for example [a malloc/realloc that fails and returns NULL will probably lead to corruption and SIGSEGV](http://etalabs.net/overcommit.html))
+
+-->
diff --git a/debian/control b/debian/control
index a660ede..7ecbc03 100644
--- a/debian/control
+++ b/debian/control
@@ -4,8 +4,13 @@ Priority: optional
Maintainer: Anton Luka Šijanec <anton@sijanec.eu>
Build-Depends: debhelper (>=11~),
libcjson-dev,
- libcurl4-openssl-dev,
- libncursesw6
+ xxd,
+ libconfig-dev,
+ libwebsockets-dev,
+ libgtk-3-dev,
+ libsoundio-dev,
+ gcc,
+ make
Standards-Version: 4.1.4
Homepage: https://git.sijanec.eu/sijanec/discord.c
@@ -13,4 +18,4 @@ Package: discord.c
Architecture: any
Multi-Arch: foreign
Depends: ${misc:Depends}, ${shlibs:Depends}
-Description: a lightweight chat client that connects to the discord.com chatting platform
+Description: a native chat client that connects to the discord.com chatting platform
diff --git a/src/api.c b/src/api.c
index ed5f06f..e69de29 100644
--- a/src/api.c
+++ b/src/api.c
@@ -1,771 +0,0 @@
-#pragma once
-#include <sys/types.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <curl/curl.h>
-#include <unistd.h>
-#include <i18n.h>
-#include <string.h>
-#include <lib.c>
-#include <cjson/cJSON.h>
-#include <time.h>
-#include <stdarg.h>
-#include <printf.h>
-#include <pthread.h>
-#include <stdatomic.h>
-#include <signal.h>
-#define DC_API_PREFIX "https://discord.com/api/v8/" /* this can be a format string, DO NOT use format characters inside */
-#define DC_LOGIN_FORMAT "{\"login\":\"%s\",\"password\":\"%s\",\"undelete\":false,\"captcha_key\":null,\"login_source\":null,\"gift_code_sku_id\":null}"
-#define DC_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
-#define DC_ERROR(c, m, ...) (dc_push_error(c, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__))
-/* #define DC_ERROR(c, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)) */
-#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */
-#define DC_CAPI(c, body, endpoint, ...) dc_api(c, body, 0##__VA_OPT__(1), endpoint __VA_OPT__(,) __VA_ARGS__)
-#define cJSON_GetObjectItem2(root, name1, name2) (cJSON_GetObjectItem(root, name1) ? cJSON_GetObjectItem(cJSON_GetObjectItem(root, name1), name2) : NULL)
-#define DC_TIMESTAMP_FORMAT "%Y-%m-%dT%H:%M:%S.XXXXXX%z"
-#define DC_SHORTTIMESTAMP_FORMAT "%Y-%m-%dT%T%z"
-#define DC_CWLE(c, name) (pthread_rwlock_wrlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0)
-#define DC_CRLE(c, name) (pthread_rwlock_rdlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0)
-#define DC_CUE(c, name) (pthread_rwlock_unlock(name) ?(DC_CLIENT_ERROR(c, DC_I18N_UNLOCKING " " #name " " DC_I18N_FAILED) || 1) : 0)
-/*
- struct members comment information:
- - noui: the ui thread must not use the member (for example curl pointers are noui)
- - noapi: the api thread must not use the member
- - nofree: the (string) pointer is of a static string, do not attempt to free it (for example function, file, message in dc_error)
- - yesfree: the (string) pointer is of a dynamically-allocated string, free it before losing pointer to the struct (most strings)
- - nouiw: ui must not write
- - noapiw: api must not write
- - nolock: no lock is required if only reading is done after a single write to the memory area or only one thread accesses it
- guidelines:
- - when threading, you must use mutexes to prevent accessing structs (and their pointers because of realloc!) (optionaly with timeouts that are optionaly reported with DC_CLIENT_ERROR)
- - error reporting function shall not report an error to itself when mutex lock times out for obvious reasons
- - _sizeofs shall represent the size of arrays in atomic types
- - keep arrays as arrays of pointers, not direct structs, as that allows pointing to individual elements without them changing pointer addresses
- - threading and races 101: when popping arrays, always lock in a non-shared way, same obviously applies for sorting, writing, appending (because of reallocs of pointer arrays)
- mutexes:
- - mutexes for child members are kept in the struct with the object that is being modified. if realloc is used on strings or those child objects (pointers change), the whole struct must be locked.
- - when reading arrays of pointers, you shared lock it
- - when writing, lock the memory in a non shared way, take note of the pointer in case of realloc
- struct pointed to from multiple sections (a great example is a channel)
- - when a channel is created, DO NOT destroy it - never free channels if you don't free messages, never free guilds if you don't free channels. that's because channels in guilds are backpointed to parent guilds and messages are backpointed to channels, realloc would lead to a segfault.
- - use posixes pthread_rwlock_init(3POSIX) mutexes to allow reading to arrays and other types
- - do not have the illusion that you can append to an array of pointers without non-shared locking, because you realloc that array of pointers, appending must also be non-shared locked
- - _free functions shall never fail - do not use timed locks
- - i changed arrays from type arrays to type pointer arrays, so code that allocates must be reimplemented, also struct free functions and -> instead of . in array elements
-*/
-struct dc_error {
- size_t line;
- const char * function; /* nofree */
- char * file; /* nofree */
- char * message; /* yesfree */
- time_t time;
- short unsigned int reported /* 0: error was not yet shown to the user, 1: error was already shown to the user */;
-};
-void dc_error_free(struct dc_error * e) { /* noui, noapi, nolock - only called by dc_client_free */
- free(e->message); e->message = NULL; /* other strings are static */
- e->line = 0;
- e->function = NULL;
- e->file = NULL;
- e->reported = 0;
- free(e);
-}
-struct dc_message {
- char * username; /* yesfree */
- int discriminator;
- char * content; /* yesfree */
- char * attachment; /* yesfree - url to the attachment should it exist */
- time_t time;
- _Atomic(struct dc_channel *) channel; /* nofree */
- unsigned short int tts;
- unsigned long long int id; /* nouiw */
- unsigned short int status; /* noapiw - 0 if it was not yet printed on the display, 1 if it was */
-};
-void dc_message_free (struct dc_message * m) { /* noui, noapi, nolock - only called by dc_channel_free */
- free(m->username); m->username = NULL;
- free(m->content); m->content = NULL;
- free(m->attachment); m->attachment = NULL;
- m->channel = NULL;
- m->tts = 0;
- m->discriminator = -1;
- m->id = 0;
- /* i'm not sure how to "unset" time_t ... */
- free(m);
-}
-int dc_message_compare (const void * a, const void * b) {
- struct dc_message * c = *(struct dc_message **) a;
- struct dc_message * d = *(struct dc_message **) b;
- /* strežnik v arrayu najprej pošlje sporočila iz sedanjosti, nato tista iz preteklosti, zato bo tudi sortiran array takšen */
- return c->time > d->time ? -1 : c->time < d->time ? 1 : 0;
-}
-struct dc_channel {
- char * name; /* yesfree, nouiw */
- char * topic; /* yesfree, nouiw */
- unsigned long long int id; /* nouiw */
- _Atomic(struct dc_guild *) guild; /* nofree, nouiw */
- struct dc_message ** messages; /* yesfree, nouiw */
- _Atomic(size_t) messages_sizeof; /* nouiw */
- int slowmode; /* nouiw - number of seconds to wait in case of slowmode, HANDLED BY THE USER INTERFACE!! */
- unsigned short int joined; /* noapiw, if joined (1) api will fetch messages and ui display them. api will still send messages even if this is not set */
- unsigned short int focused; /* noapi, if focused (1) ui will send messages to this channel */
-};
-void dc_channel_free (struct dc_channel * ch) { /* noui, noapi, nolock - only called by dc_guild_free */
- free(ch->name); ch->name = NULL;
- free(ch->topic); ch->topic = NULL;
- ch->guild = NULL;
- ch->id = 0;
- for (int i = 0; i < ch->messages_sizeof; i++)
- dc_message_free(ch->messages[i]);
- free(ch->messages);
- ch->messages_sizeof = 0;
- free(ch);
-}
-struct dc_guild {
- char * name; /* nouiw, yesfree */
- unsigned long long id; /* nouiw */
- _Atomic(size_t) channels_sizeof; /* nouiw */
- struct dc_channel ** channels; /* yesfree, nouiw */
- _Atomic(struct dc_client *) client; /* nofree - obviously */
- char * altmsgurl; /* yesfree - alternative messages url - used for virtual guild for DMs */
-};
-void dc_guild_free (struct dc_guild * g) { /* noui, noapi, nolock - only called by dc_client_free */
- free(g->name); g->name = NULL;
- g->id = 0;
- for (int i = 0; i < g->channels_sizeof; i++)
- dc_channel_free(g->channels[i]);
- free(g->channels);
- g->channels_sizeof = 0;
- free(g->altmsgurl);
- free(g);
-}
-struct dc_client {
- CURL * curl; /* noui, nolock */
- struct curl_slist * curl_headers; /* noui, nolock */
- char * authorization; /* nouiw */
- pthread_rwlock_t * authorization_lock;
- char * email; /* yesfree, noapiw, nolock */
- char * password; /* yesfree, noapiw, nolock */
- char * username; /* yesfree, nouiw */
- pthread_rwlock_t * username_lock;
- _Atomic(int) discriminator; /* nouiw - -1: the user is not logged in, -2: user login failed and will not be retried, < 0: the user is not logged in */
- struct dc_guild ** guilds; /* yesfree, nouiw */
- _Atomic(size_t) guilds_sizeof; /* nouiw */
- pthread_rwlock_t * guilds_lock;
- struct dc_error ** errors; /* yesfree */
- _Atomic(size_t) errors_sizeof;
- pthread_rwlock_t * errors_lock;
- struct dc_message ** sent_messages; /* yesfree - ui appends, api pops and moves to messages */
- _Atomic(size_t) sent_messages_sizeof;
- pthread_rwlock_t * sent_messages_lock;
- _Atomic(time_t) last_sent_message; /* for slowmode implementations */
- _Atomic(short unsigned int) newmessages; /* > 0 if api got new messages. ui resets to 0 - nolock - ui should still check without this flag for new messages - racy */
-};
-struct dc_client * dc_client_init () { /* gives you a prepared dc_client */
- struct dc_client * c = calloc(1, sizeof(struct dc_client));
- c->discriminator = -1;
- c->guilds_sizeof = 1;
- c->guilds = calloc(1, sizeof(struct dc_guild *));
- c->guilds[0] = calloc(1, sizeof(struct dc_guild));
- c->guilds[0]->name = malloc(strlen(DC_I18N_DMS)+1);
- strcpy(c->guilds[0]->name, DC_I18N_DMS);
- c->guilds[0]->altmsgurl = malloc(strlen(DC_API_PREFIX "users/@me/channels")+1);
- strcpy(c->guilds[0]->altmsgurl, DC_API_PREFIX "users/@me/channels");
- c->guilds[0]->client = c;
-#define DC_CILI(name) /* Client Init Lock Init */ do { name##_lock = malloc(sizeof(pthread_rwlock_t)); pthread_rwlock_init(name##_lock, NULL); } while(0)
- DC_CILI(c->authorization);
- DC_CILI(c->username);
- DC_CILI(c->guilds);
- DC_CILI(c->errors);
- DC_CILI(c->sent_messages);
- return c;
-}
-void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only called by main on exit */
- curl_easy_cleanup(c->curl);
- c->curl = NULL;
- curl_slist_free_all(c->curl_headers);
- c->curl_headers = NULL;
- free(c->authorization); c->authorization = NULL;
- free(c->email); c->email = NULL;
- free(c->password); c->password = NULL;
- free(c->username); c->username = NULL;
- c->discriminator = -1;
- for (int i = 0; i < c->guilds_sizeof; i++)
- dc_guild_free(c->guilds[i]);
- free(c->guilds);
- c->guilds_sizeof = 0;
- for (int i = 0; i < c->errors_sizeof; i++)
- dc_error_free(c->errors[i]);
- free(c->errors);
- c->errors_sizeof = 0;
- for (int i = 0; i < c->sent_messages_sizeof; c++)
- dc_message_free(c->sent_messages[i]);
- free(c->sent_messages);
- c->sent_messages_sizeof = 0;
-#define DC_CFLD(name) do { pthread_rwlock_destroy(name##_lock); free(name##_lock); } while(0)
- DC_CFLD(c->authorization);
- DC_CFLD(c->username);
- DC_CFLD(c->guilds);
- DC_CFLD(c->errors);
- DC_CFLD(c->sent_messages);
- free(c);
-}
-int dc_push_error (struct dc_client * c, const char * caller, char * f, size_t l, unsigned short int isfmt, char * m, ...) {
-#define DC_PEE /* dc scalnia oziroma push error error */ c->errors[c->errors_sizeof-1]
- if (!c)
- return -2;
- pthread_rwlock_t * lock = c->errors_lock;
- if (!lock)
- return -3;
- if (pthread_rwlock_wrlock(lock))
- return -1; /* does not report an error as that may make things even worse. I could try writing to stderr here but meh */
- c->errors = realloc(c->errors, sizeof(struct dc_error *)*++c->errors_sizeof); /* note: format arguments are evaluated twice */
- DC_PEE = malloc(sizeof(struct dc_error));
- size_t strlenm = strlen(m);
- size_t va_count = parse_printf_format(m, 0, NULL);
- if (isfmt && va_count > 0) {
- va_list ap, ap2;
- va_start(ap, m);
- va_copy(ap2, ap);
- strlenm = vsnprintf(NULL, 0, m, ap);
- DC_PEE->message = malloc(sizeof(char)*strlenm+1);
- vsnprintf(DC_PEE->message, strlenm+1, m, ap2);
- va_end(ap);
- va_end(ap2);
- } else {
- DC_PEE->message = malloc(sizeof(char)*strlenm+1);
- strcpy(DC_PEE->message, m);
- }
- DC_PEE->file = f;
- DC_PEE->line = l;
- DC_PEE->function = caller /* Caller */;
- DC_PEE->time = time(NULL);
- DC_PEE->reported = 0;
- if (lock && pthread_rwlock_unlock(lock))
- return -2;
- return 1;
-}
-cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */
- if (!c)
- return NULL;
- if (!c->curl) {
- DC_CLIENT_ERROR(c, "!c->curl");
- }
- if (!c->curl) {
- DC_CLIENT_ERROR(c, "!c->curl");
- return NULL;
- }
- if (!endpoint) {
- DC_CLIENT_ERROR(c, "!endpoint");
- return NULL;
- }
- cJSON * json = NULL;
- struct writefunc_string * s = malloc(sizeof(struct writefunc_string));
- struct writefunc_string * h = malloc(sizeof(struct writefunc_string));
- size_t va_count = parse_printf_format(endpoint, 0, NULL);
- char * endpoint_formatted = NULL;
- int retried = 0;
- long response_code = 0;
- retry:
- init_writefunc_string(s);
- init_writefunc_string(h);
- if (isfmt && va_count > 0 && endpoint_formatted == NULL) {
- va_list ap, ap2;
- va_start(ap, endpoint);
- va_copy(ap2, ap);
- size_t strlenm = vsnprintf(NULL, 0, endpoint, ap);
- endpoint_formatted = malloc(sizeof(char)*strlenm+1);
- vsnprintf(endpoint_formatted, strlenm+1, endpoint, ap2); /* sn instead of s because double evaulation may produce */
- va_end(ap); /* larger output the next time and lead to overflow */
- va_end(ap2);
- }
- curl_easy_setopt(c->curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint);
- curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, body); /* yes, even null is okay, it's actually a must as it makes sure curl clears old pointers and does not read freed memory. see https://github.com/curl/curl/issues/3214#issuecomment-435335974 */
- if (!body)
- curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); /* this must be done after postfields */
- curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, s);
- curl_easy_setopt(c->curl, CURLOPT_NOSIGNAL, 1L);
- curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, h);
- curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, 1L);
- curl_easy_setopt(c->curl, CURLOPT_HTTPHEADER, c->curl_headers);
- curl_easy_setopt(c->curl, CURLOPT_TIMEOUT, 20L); /* 20 second timeout */
- curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, writefunc);
- curl_easy_setopt(c->curl, CURLOPT_WRITEFUNCTION, writefunc);
- if (curl_easy_perform(c->curl) != CURLE_OK) {
- DC_CLIENT_ERROR(c, "curl_easy_perform(curl) != CURLE_OK");
- goto rc;
- } else {
- curl_easy_getinfo(c->curl, CURLINFO_RESPONSE_CODE, &response_code);
- }
-#ifdef DC_NETREQ
- fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h->ptr, s->ptr);
- fflush(netreq);
-#endif
- json = cJSON_Parse(s->ptr);
- if (!json) {
- const char * error_ptr = cJSON_GetErrorPtr();
- if (error_ptr) {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %.21s s->ptr = %s", error_ptr, s->ptr);
- } else {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ". s->ptr = %s", s->ptr ? s->ptr : "NULL");
- }
- goto rc;
- }
- /* note that the server does not send x-ratelimit headers for normal users and cJSON has problems with parsing floats, so caveman approach is needed */
- char * cp = cJSON_GetStringValue(cJSON_GetObjectItem(json, "retry_after"));
- if (cp && (cp = strstr(cp, "\"retry_after\": "))) { /* note: make sure to always check if retry_after is in the root of the object, otherwise you can get dosed if a user somehow inserts "retry_after": into a message or something. we could check error code though, but naaaah */
- if (!(cp = strchr(cp, ' ')))
- goto rc;
- int retry_after = atoi(++cp)+1;
- DC_CLIENT_ERROR(c, DC_I18N_HITRL " %lfs. endpoint = %s", retry_after, endpoint_formatted ? endpoint_formatted : endpoint);
- sleep(retry_after); /* TODO: prevent hanging entire thread just for this */
- free(s->ptr); s->ptr = NULL;
- free(h->ptr); h->ptr = NULL;
- free(s); s = NULL;
- free(h); h = NULL;
- cJSON_Delete(json);
- json = NULL;
- if (retried++)
- return NULL;
- goto retry;
- }
- rc:
- curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, NULL); /* for usages that don't use headerfunction */
- free(endpoint_formatted);
- free(s->ptr); s->ptr = NULL;
- free(h->ptr); h->ptr = NULL;
- free(s); s = NULL;
- free(h); h = NULL;
- return json;
-}
-int dc_login (struct dc_client * c) { /* noui */
- int rs = 1;
- char * data = NULL;
- cJSON * json = NULL;
- if (!c)
- return -1;
- if (!c->email || !c->password) {
- DC_CLIENT_ERROR(c, DC_I18N_MISSING_EP);
- return -2;
- }
- if (!c->curl)
- c->curl = curl_easy_init();
- if (!c->curl) {
- DC_CLIENT_ERROR(c, "curl_easy_init() " DC_I18N_FAILED);
- return -3;
- }
- data = malloc(snprintf(NULL, 0, DC_LOGIN_FORMAT, c->email, c->password)+1);
- sprintf(data, DC_LOGIN_FORMAT, c->email, c->password);
- /* curl_slist_free_all(c->curl_headers); */
- c->curl_headers = curl_slist_append(c->curl_headers, "Content-Type: application/json");
- c->curl_headers = curl_slist_append(c->curl_headers, "User-Agent: " DC_USER_AGENT);
- json = DC_CAPI(c, data, DC_API_PREFIX "auth/login");
- if (!json) {
- DC_CLIENT_ERROR(c, "!DC_CAPI auth/login, data = %s", data);
- rs = -4;
- goto rc;
- }
- cJSON * token = cJSON_GetObjectItem(json, "token");
- if (!cJSON_IsString(token)) {
- DC_CLIENT_ERROR(c, "!cJSON_IsString(token)");
- rs = -6;
- goto rc;
- }
- if (DC_CWLE(c, c->authorization_lock)) {rs = -7; goto rc;}
- c->authorization = realloc(c->authorization, strlen(token->valuestring)+1);
- strcpy(c->authorization, token->valuestring);
- data = realloc(data, strlen(c->authorization)+strlen("Authorization: ")+1+1/* last one because valgrind was complaining */);
- strcpy(data, "Authorization: ");
- strcat(data, c->authorization);
- if (DC_CUE(c, c->authorization_lock)) {rs = -8; goto rc;}
- c->curl_headers = curl_slist_append(c->curl_headers, data);
- cJSON_Delete(json);
- json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me");
- if (!json) {
- DC_CLIENT_ERROR(c, "!DC_CAPI users/@me");
- rs = -7;
- goto rc;
- }
- token = cJSON_GetObjectItem(json, "username");
- cJSON * token2 = cJSON_GetObjectItem(json, "discriminator");
- if (!cJSON_IsString(token) || !cJSON_IsString(token2)) {
- DC_CLIENT_ERROR(c, "!cJSON_IsString(token) || !cJSON_IsString(token2)");
- rs = -11;
- goto rc;
- }
- if (DC_CWLE(c, c->username_lock)) {rs = -11; goto rc;}
- c->username = realloc(c->username, strlen(token->valuestring)+1);
- strcpy(c->username, token->valuestring);
- if (DC_CUE(c, c->username_lock)) {rs = -12; goto rc;}
- c->discriminator = strtol(token2->valuestring, NULL, 10);
- rc:
- free(data); data = NULL;
- cJSON_Delete(json);
- return rs;
-}
-int dc_fetch_guilds (struct dc_client * c) {
- if (!c)
- return -5;
- int rs = 1;
- char * value = NULL;
- char * value2 = NULL;
- cJSON * json = NULL;
- if (c->discriminator < 0) {
- if ((rs = dc_login(c)) < 0) {
- DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
- return -1;
- } else rs = 1;
- }
- json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me/guilds");
- if (!json) {
- DC_CLIENT_ERROR(c, "DC_CAPI users/@me/guilds " DC_I18N_FAILED);
- rs = -3;
- goto rc;
- }
- if (!cJSON_IsArray(json)) {
- DC_CLIENT_ERROR(c, "!cJSON_IsArray(json)");
- rs = -4;
- goto rc;
- }
- if(DC_CWLE(c, c->guilds_lock)) {rs = -5; goto rc;}
- cJSON * guild = NULL;
- cJSON_ArrayForEach(guild, json) {
- int skip = 0;
- value = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "name"));
- value2 = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "id"));
- if (!value || !value2) {
- char * jsonstr = cJSON_Print(json);
- DC_CLIENT_ERROR(c, "!cJSON_GetStringValue(cJSON_GetObjectItem(json, \"id\" || \"name\")) json = %s", jsonstr);
- free(jsonstr);
- continue;
- }
- unsigned long long int idull = strtoull(value2, NULL, 10);
- for (int i = 0; i < c->guilds_sizeof; i++)
- if (idull == c->guilds[i]->id) {
- skip = 1;
- break; /* remove duplicates */
- }
- if (skip) continue;
- c->guilds = realloc(c->guilds, sizeof(struct dc_guild *)*++c->guilds_sizeof);
- c->guilds[c->guilds_sizeof-1] = calloc(1, sizeof(struct dc_guild));
- c->guilds[c->guilds_sizeof-1]->name = malloc(strlen(value)+1);
- strcpy(c->guilds[c->guilds_sizeof-1]->name, value);
- c->guilds[c->guilds_sizeof-1]->id = idull;
- c->guilds[c->guilds_sizeof-1]->client = c;
- }
- if(DC_CUE(c, c->guilds_lock)) {rs = -6; goto rc;}
- rc:
- cJSON_Delete(json); json = NULL;
- return rs;
-}
-int dc_fetch_channels (struct dc_guild * g) {
- int rs = 1;
- if (!g)
- return -1;
- struct dc_client * c = g->client;
- if (!c)
- return -2;
- if (c->discriminator < 0) {
- if ((rs = dc_login(c)) < 0) {
- DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
- return -3;
- } else rs = 1;
- }
- cJSON * json = NULL;
- if (g->altmsgurl)
- json = DC_CAPI(c, NULL, g->altmsgurl); /* used for private dm channels */
- else
- json = DC_CAPI(c, NULL, DC_API_PREFIX "guilds/%llu/channels", g->id); /* ids are only written by api thread, nolock */
- if (!json) {
- DC_CLIENT_ERROR(c, "DC_CAPI id = %llu, g->altmsgurl = %s" DC_I18N_FAILED, g->id, g->altmsgurl ? g->altmsgurl : "NULL" /* static string */);
- rs = -5;
- goto rc;
- }
- if (!cJSON_IsArray(json)) {
- char * jsonstr = cJSON_Print(json);
- DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), g->id = %llu, g->altmsgurl = %s, json = %s", g->id, g->altmsgurl ? g->altmsgurl : "NULL", jsonstr);
- free(jsonstr);
- rs = -6;
- goto rc;
- }
- cJSON * channel = NULL;
- /* cJSON * perms = NULL; */ /* I'll rather not remove the channels with perms and let the user get banned for joining a channel without perms */
- /* cJSON * perm = NULL; */
- /* we lock all client guilds when doing stuff with channels */
- if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;}
- cJSON_ArrayForEach(channel, json) {
- int skip = 0;
- /* if (cJSON_IsArray(perms = cJSON_GetObjectItem(channel, "permission_overwrites")));
- cJSON_ArrayForEach(perm, perms) {
- skip++;
- }
- if (skip) continue; */
- char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "topic"));
- char * name = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "name"));
- char * id = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "id"));
- cJSON * type = cJSON_GetObjectItem(channel, "type");
- cJSON * jsonslowmode = cJSON_GetObjectItem(channel, "rate_limit_per_user");
- if (!cJSON_IsNumber(type) || !id) {
- char * krneki = cJSON_Print(channel);
- DC_CLIENT_ERROR(c, "!cJSON_IsNumber(type) || !id, channel = %s", krneki);
- free(krneki); krneki = NULL;
- continue;
- }
- if (type->valueint != 0 && type->valueint != 3 && type->valueint != 1)
- continue;
- if (!name) {
- cJSON * recipients = cJSON_GetObjectItem(channel, "recipients");
- if (!cJSON_IsArray(recipients))
- continue;
- cJSON * rec = NULL;
- name = calloc(1, 1);
- cJSON_ArrayForEach(rec, recipients) {
- char * recipient = cJSON_GetStringValue(cJSON_GetObjectItem(rec, "username"));
- if (!recipient)
- continue;
- name = realloc(name, strlen(name)+strlen(recipient)+3); /* +3: comma, space, null character */
- strcat(name, recipient);
- strcat(name, ", ");
- }
- name[strlen(name)-2] = '\0'; /* odrežemo zadnja dva znaka; vejico in presledek I: */
- }
- int slowmode = 0;
- if (cJSON_IsNumber(jsonslowmode))
- slowmode = jsonslowmode->valueint;
- if (!topic)
- topic = "";
- unsigned long long int idull = strtoull(id, NULL, 10);
- for (int i = 0; i < g->channels_sizeof; i++)
- if (idull == g->channels[i]->id) {
- skip++;
- break;
- }
- if (skip) continue;
- g->channels = realloc(g->channels, sizeof(struct dc_channel *)*++g->channels_sizeof);
- g->channels[g->channels_sizeof-1] = calloc(1, sizeof(struct dc_channel));
- g->channels[g->channels_sizeof-1]->name = malloc(strlen(name)+1);
- strcpy(g->channels[g->channels_sizeof-1]->name, name);
- g->channels[g->channels_sizeof-1]->topic = malloc(strlen(topic)+1);
- strcpy(g->channels[g->channels_sizeof-1]->topic, topic);
- g->channels[g->channels_sizeof-1]->id = strtoull(id, NULL, 10);
- g->channels[g->channels_sizeof-1]->guild = g;
- g->channels[g->channels_sizeof-1]->slowmode = slowmode;
- }
- if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;}
- rc:
- cJSON_Delete(json); json = NULL;
- return rs;
-}
-int dc_send_message (struct dc_message * m) { /* nolock - once message is appended to the c->sent_messages queue you must not remove it or alter it */
- int rs = 1;
- if (!m)
- return -1;
- struct dc_client * c = m->channel->guild->client; /* segfault senpai */
- if (!c)
- return -2;
- if (c->discriminator < 0) {
- if ((rs = dc_login(c)) < 0) {
- DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
- return -3;
- } else rs = 1;
- }
- cJSON * json = cJSON_CreateObject();
- cJSON * nons = cJSON_CreateNumber(rand());
- cJSON_AddItemToObject(json, "nonce", nons);
- cJSON * content = cJSON_CreateString(m->content ? m->content : "dc_send_message(): !m->content");
- cJSON_AddItemToObject(json, "content", content);
- cJSON * tts = m->tts ? cJSON_CreateTrue() : cJSON_CreateFalse();
- cJSON_AddItemToObject(json, "tts", tts);
- char * body = cJSON_Print(json);
- if (!body) {
- DC_CLIENT_ERROR(c, "cJSON_Print " DC_I18N_FAILED);
- rs = -4;
- goto rc;
- }
- cJSON_Delete(json); json = NULL;
- /* {content: "yeet", nonce: "820762917392613376", tts: false} */
- json = DC_CAPI(c, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id); /* ids are only written by api thread, nolock */
- if (!json) {
- DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages " DC_I18N_FAILED, m->channel->id);
- rs = -5;
- goto rc;
- }
- char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "discriminator"));
- if (!discriminator) {
- DC_CLIENT_ERROR(c, "!discriminator");
- rs = -6;
- goto rc;
- }
- m->discriminator = strtol(discriminator, NULL, 10);
- char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "username"));
- if (!username) {
- DC_CLIENT_ERROR(c, "!username");
- rs = -6;
- goto rc;
- }
- m->username = malloc(strlen(username)+1);
- strcpy(m->username, username); /* we don't directly point as that changes when we delete */
- rc:
- free(body); body = NULL;
- cJSON_Delete(json); json = NULL;
- c->last_sent_message = time(NULL);
- return rs;
-}
-int dc_fetch_messages (struct dc_channel * ch) {
- int rs = 1;
- struct tm tm;
- if (!ch || !ch->id)
- return -1;
- struct dc_guild * g = ch->guild;
- if (!g || !g->client)
- return -2;
- struct dc_client * c = g->client;
- if (!c)
- return -3;
- if (c->discriminator < 0) {
- if ((rs = dc_login(c)) < 0) {
- DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
- return -4;
- } else rs = 1;
- }
- cJSON * json = DC_CAPI(c, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100&_=%d", ch->id, rand());
- if (!json) {
- DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages?limit=100&_=***rand()*** " DC_I18N_FAILED, ch->id);
- rs = -5;
- goto rc;
- }
- if (!cJSON_IsArray(json)) {
- DC_CLIENT_ERROR(c, "!cJSON_IsArray(json)");
- rs = -6;
- goto rc;
- }
- if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} /* we lock all guilds of a client when writing messages */
- cJSON * message = NULL;
- int msgs = 0;
- cJSON_ArrayForEach(message, json) {
- int skip = 0;
- char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(message, "timestamp"));
- char * content = cJSON_GetStringValue(cJSON_GetObjectItem(message, "content"));
- char * id = cJSON_GetStringValue(cJSON_GetObjectItem(message, "id"));
- char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(message, "author", "discriminator"));
- char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(message, "author", "username"));
- char * attachment = NULL;
- cJSON * attachmentj = NULL;
- cJSON * attachments = cJSON_GetObjectItem(message, "attachments");
- cJSON_ArrayForEach(attachmentj, attachments)
- if ((attachment = cJSON_GetStringValue(cJSON_GetObjectItem(attachmentj, "url"))))
- break; /* we only extract the first attachment */
- if (!id || !timestamp || (!content && !attachment) || !username || !discriminator) {
- DC_CLIENT_ERROR(c, "!id || (!timestamp && !attachment) || !content || !username || discriminator < 0");
- continue;
- }
- int kratekts = 0;
- if (strlen(timestamp) < 26) {
- if (strlen(timestamp) == strlen("2021-03-21T13:54:17+00:00")) {
- kratekts = 1;
- } else {
- char * jsonstring = cJSON_Print(message);
- DC_CLIENT_ERROR(c, "strlen(timestamp) < 26, json = %s", jsonstring);
- free(jsonstring);
- jsonstring = NULL;
- continue;
- }
- }
- if (!kratekts) {
- for (int i = 20; i <= 25; i++)
- timestamp[i] = 'X'; /* because strptime does not have wildcard support and those numbers are sub-second fractions */
- if (!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm)) {
- DC_CLIENT_ERROR(c, "!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm), timestamp = %s", timestamp);
- continue;
- }
- } else { /* if there are no subsecond fractions */
- if (!strptime(timestamp, DC_SHORTTIMESTAMP_FORMAT, &tm)) {
- DC_CLIENT_ERROR(c, "!strptime(timestamp, DC_SHORTTIMESTAMP_FORMAT, &tm), timestamp = %s", timestamp);
- continue;
- }
- }
- unsigned long long int idull = strtoull(id, NULL, 10);
- for (int i = 0; i < ch->messages_sizeof; i++)
- if (idull == ch->messages[i]->id) {
- skip++;
- break; /* remove duplicates */
- }
- if (skip) continue;
- ch->messages = realloc(ch->messages, sizeof(struct dc_message *)*++ch->messages_sizeof);
-#define DC_FMTM /* fetch messages this message */ ch->messages[ch->messages_sizeof-1]
- /* DC_CLIENT_ERROR(c, "recvd msg %llu", idull); */ /* remember: continue in a nested forloop is not useful in some cases (: */
- DC_FMTM = calloc(1, sizeof(struct dc_message));
- DC_FMTM->time = mktime(&tm);
- if (content)
- strcpy(DC_FMTM->content = malloc(strlen(content)+1), content);
- if (attachment)
- strcpy(DC_FMTM->attachment = malloc(strlen(attachment)+1), attachment);
- DC_FMTM->username = malloc(strlen(username)+1);
- strcpy(DC_FMTM->username, username);
- DC_FMTM->id = idull;
- DC_FMTM->discriminator = strtol(discriminator, NULL, 10);
- DC_FMTM->channel = ch;
- msgs++;
- }
- qsort(ch->messages, ch->messages_sizeof, sizeof(struct dc_message *), dc_message_compare); /* we sort so that present messages are in the start of the array and old messages are to the end of the array */
- if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;}
- if (msgs > 0)
- rs = msgs;
- rc:
- cJSON_Delete(json); json = NULL;
- return rs;
-}
-struct dc_thread_control {
- unsigned short int power_api; /* 1 if the thread should run, set it to 2 for the thread to return at the end of the loop */
- unsigned short int power_ui; /* so same struct can be used for both api and ui thread, they have individual power switches */
- /* powers have a default state of 0, in this state, the thread should be paused, before first clearing 0 the thread should not do anything! */
- struct dc_client ** clients; /* "array" of pointers to clients the thread should manage, ONLY ONE dc_api_thread PER PROCESS! */
- _Atomic(size_t) clients_sizeof; /* noapiw */
- pthread_rwlock_t * clients_lock; /* do not lock yet. you can use safe-appending from the ui thread by never reallocing the pointers and only incrementing _sizeof - don't even init&destroy*/
- FILE * cout; /* file descriptor of the terminal for the ui thread to write to */
- FILE * cin; /* file descriptor of the terminal for the ui thread to read from */
- FILE * cerr; /* file descriptor of the terminal for the ui thread to write error messages to */
-};
-int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends messages when they are in the outbox */
- while (!t->power_api) usleep(250000); /* so as to not make the switcher go bankrupt */
- curl_global_init(CURL_GLOBAL_ALL);
- /* if (pthread_rwlock_wrlock(t->clients_lock))
- return -1; */ /* clients are not locked yet */
- for (int i = 0; i < t->clients_sizeof && t->power_api != 2; i++)
- dc_login(t->clients[i]);
- while (t->power_api != 2) { /* as there's only one api thread and only it modifies things that need a guilds_lock, it's okay to */
- for (int i = 0; i < t->clients_sizeof; i++) { /* perform such unsafe loops without read-locking. note that you will deadlock */
- if (t->clients[i]->discriminator < -1) /* should you attempt to read-lock guilds_lock. */
- continue; /* the only exception is sent_messages that is write-locked */
- if (t->clients[i]->guilds_sizeof <= 1 /* || !(rand() % 1000) */) /* guild 0 is a virtual ZS guild */
- dc_fetch_guilds(t->clients[i]);
- for (int j = 0; j < t->clients[i]->guilds_sizeof; j++) {
- if (!t->clients[i]->guilds[j]->channels_sizeof /*|| !(rand() % 100)*/) /* roughly every 100+inf cycles we'll update channels */
- dc_fetch_channels(t->clients[i]->guilds[j]);
- }
- for (int k = 0; k < t->clients[i]->guilds_sizeof; k++)
- for (int l = 0; l < t->clients[i]->guilds[k]->channels_sizeof; l++)
- if (t->clients[i]->guilds[k]->channels[l]->joined && !(rand() % 10)) /* roughly every 10 cycles we'll update messages in the joined channels */
- if (dc_fetch_messages(t->clients[i]->guilds[k]->channels[l]) > 0)
- t->clients[i]->newmessages++;
- if (DC_CWLE(t->clients[i], t->clients[i]->sent_messages_lock)) continue;
- if (t->clients[i]->sent_messages_sizeof > 0) {
- struct dc_message * msg2send = t->clients[i]->sent_messages[0];
- if (dc_send_message(msg2send) > 0) {
- /* DC_CWLE(t->clients[i], t->clients[i]->guilds_lock);
- msg2send->channel->messages = realloc(msg2send->channel->messages, sizeof(struct dc_message *)*++msg2send->channel->messages_sizeof);
- msg2send->channel->messages[msg2send->channel->messages_sizeof-1] = msg2send;
- DC_CUE(t->clients[i], t->clients[i]->guilds_lock); */ /* we will no longer do this, let the thread fetch the msg */
- for (int j = 0; j < t->clients[i]->sent_messages_sizeof-1; j++) /* shift, we removed one from the start */
- t->clients[i]->sent_messages[j] = t->clients[i]->sent_messages[j+1];
- t->clients[i]->sent_messages_sizeof--;
- }
- }
- DC_CUE(t->clients[i], t->clients[i]->sent_messages_lock);
- while (!t->power_api) usleep(250000); /* so as to not make the switcher go bankrupt */
- }
- usleep(250000);
- }
- curl_global_cleanup();
- /* if (pthread_rwlock_unlock(t->clients_lock))
- return -2; */
- return 1;
-} /* the thread shall use mutexes when doing things with shared memory - client structs */
diff --git a/src/i18n.h b/src/i18n.h
deleted file mode 100644
index acb8946..0000000
--- a/src/i18n.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#define DC_I18N_FAILED "neuspelo" /**/
-#define DC_I18N_UNREC_ARG "neprepoznan argument %c, poskusi -h"
-#define DC_I18N_USAGE "uporaba: %s -e naslov@example -p geslo"
-#define DC_I18N_ARG_ALREADY_SET "argument %c že ima nastavljeno vrednost (mogoče okoljske spremenljivke)"
-#define DC_I18N_MISSING_EP "manjka poštni naslov in/ali geslo" /**/
-#define DC_I18N_LOGGED_IN "prijavljeni ste kot %s"
-#define DC_I18N_LOGIN_FAILED "prijava neuspela. prijavite se v brskalniku. strežnik je odgovoril: %s"
-#define DC_I18N_NOT_JOINED "preden lahko pišeš, se moraš pridružiti kanalu"
-#define DC_I18N_GUILD_NOT_SET "skupina ni izbrana!"
-#define DC_I18N_CHANNEL_NOT_SET "kanal ni izbran!"
-#define DC_I18N_MESSAGES_GET_FAIL "pri pridobivanju sporočil je prišlo do napake"
-#define DC_I18N_JSON_ERROR_BEFORE "JSON napaka pred"
-#define DC_I18N_LOCKING "zaklepanje"
-#define DC_I18N_UNLOCKING "odklepanje"
-#define DC_I18N_ERROR "napaka"
-#define DC_I18N_MSGTIMEF "%e. %b %H:%M:%S" /* strftime(3) */
-#define DC_I18N_UI_USAGE "uporaba"
-#define DC_I18N_UI_CHANNELS_USAGE "/kanali <številka skupine (0-%lu)>"
-#define DC_I18N_UI_GC_USAGE "/<ukaz> <številka skupine (0-%lu)> <številka kanala v tej skupini (0-%lu)>"
-#define DC_I18N_UI_LINE_BEFORE_JOIN "discord.c | uporabi ukaz /s za prikaz skupin, /k za prikaz kanalov in /p za priklop v kanal"
-#define DC_I18N_HITRL "zmanjkalo dovoljenih zahtev na strežnik. API nit bo čakala " /* and then print seconds */
-#define DC_I18N_UI_CNF "nepoznan ukaz"
-#define DC_I18N_UI_NOT_JOINED "niste pridruženi v kanal. uporabite ukaz /pridruži"
-#define DC_I18N_CHANNEL_WILL_BE_REMOVED "kanal bo odstranjen z lokalnega seznama kanalov"
-#define DC_I18N_UI_EMPTYMSG "ne moreš poslati praznega sporočila."
-#define DC_I18N_UI_SLOWMODE "na tem kanalu po poslanem sporočilu novega ne smeš poslati %ds. počakaj še %ds in poskusi znova."
-#define DC_I18N_UI_LINE_BEFORE_NETWORK "discord.c | najprej zaženi mrežno nit z ukazom /n 1"
-#define DC_I18N_DMS "zasebni pogovori (virtualna skupina kanalov)"
-#define DC_I18N_ATTACHMENT "priponka"
diff --git a/src/lib.c b/src/lib.c
deleted file mode 100644
index 5a611a3..0000000
--- a/src/lib.c
+++ /dev/null
@@ -1,26 +0,0 @@
-struct writefunc_string {
- char *ptr;
- size_t len;
-};
-void init_writefunc_string(struct writefunc_string *s) {
- s->len = 0;
- s->ptr = malloc(s->len+1+250);
- if (s->ptr == NULL) {
- fprintf(stderr, "malloc() " DC_I18N_FAILED "\n");
- exit(EXIT_FAILURE);
- }
- s->ptr[0] = '\0';
-}
-size_t writefunc(void *ptr, size_t size, size_t nmemb, struct writefunc_string *s) {
- size_t new_len = s->len + size*nmemb;
- s->ptr = realloc(s->ptr, new_len+1+250);
- if (s->ptr == NULL) {
- fprintf(stderr, "realloc() " DC_I18N_FAILED "\n");
- exit(EXIT_FAILURE);
- }
- memcpy(s->ptr+s->len, ptr, size*nmemb);
- s->ptr[new_len] = '\0';
- s->len = new_len;
- return size*nmemb;
-}
-
diff --git a/src/main.c b/src/main.c
index df55b80..9a4ac4d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,81 +1,8 @@
-#define _XOPEN_SOURCE 600
#include <stdio.h>
-/* #define DC_NETREQ */
-#ifdef DC_NETREQ
-FILE * netreq;
-#endif
+#include <stdlib.h>
#include <ui.c>
-int main (int argc, char ** argv) {
- srand(time(NULL));
-#ifdef DC_NETREQ
- netreq = fopen("netreq.log", "w");
-#endif
- int rs = 0;
- int opt;
- pthread_t api_thread, ui_thread;
- int api_ret, ui_ret;
- struct dc_client * c = dc_client_init();
- struct dc_thread_control t = {
- .power_api = 0,
- .power_ui = 1, /* so we don't start without user interface lol */
- .clients = &c,
- .clients_sizeof = 1,
- .cout = stdout,
- .cin = stdin,
- .cerr = stderr
- };
- while ((opt = getopt(argc, argv, "e:p:h")) != -1) {
- switch (opt) {
- case 'h':
- fprintf(stdout, DC_I18N_USAGE, argv[0]);
- rs = 4;
- goto rc;
- break;
- case 'e':
- c->email = malloc(strlen(optarg)+1);
- strcpy(c->email, optarg);
- break;
- case 'p':
- c->password = malloc(strlen(optarg)+1);
- strcpy(c->password, optarg);
- break;
- default:
- fprintf(stderr, DC_I18N_UNREC_ARG "\n", opt);
- dc_client_free(c);
- rs = 1;
- goto rc;
- }
- }
- if (!c->email) {
- if (!getenv("DC_E")) {
- fprintf(stderr, DC_I18N_MISSING_EP "\n");
- rs = 2;
- goto rc;
- }
- c->email = malloc(strlen(getenv("DC_E"))+1);
- strcpy(c->email, getenv("DC_E"));
- }
- if (!c->password) {
- if (!getenv("DC_P")) {
- fprintf(stderr, DC_I18N_MISSING_EP "\n");
- rs = 3;
- goto rc;
- }
- c->password = malloc(strlen(getenv("DC_P"))+1);
- strcpy(c->password, getenv("DC_P"));
- }
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
- api_ret = pthread_create(&api_thread, NULL, dc_api_thread, &t);
- ui_ret = pthread_create(&ui_thread, NULL, dc_ui_thread, &t);
-#pragma GCC diagnostic pop
- pthread_join(api_thread, NULL);
- pthread_join(ui_thread, NULL);
- rc:
-#ifdef DC_NETREQ
- fclose(netreq);
-#endif
- dc_client_free(c);
- if (api_ret || ui_ret); /* to hide warnings */
- return rs;
+#include <api.c>
+int main (int argc, char * argv[]) {
+ dc_ui(argc, argv);
+ return 0;
}
diff --git a/src/ui.c b/src/ui.c
index 725e7cc..79aa524 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -1,378 +1,279 @@
-#pragma once
#include <stdlib.h>
-#include <stdio.h>
-#include <time.h>
-#include <api.c>
-#include <ncursesw/ncurses.h>
-#include <ncursesw/form.h>
-#include <unistd.h>
-#include <locale.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
#include <string.h>
-#include <assert.h>
-#include <signal.h>
-#define DC_SIMPLEPRINT(w, c, f, ...) do { wattron(w, COLOR_PAIR(c)); wprintw(w, f __VA_OPT__(,) __VA_ARGS__); wrefresh(w); } while (0)
-/* link with -lncursesw and -lformw */
-void dc_null() {
- return; /* a simple null function */
-}
-int dc_ui_print_message (WINDOW * textwin, struct dc_message * msg2do) {
- int y, x;
- char timestring[64];
- struct tm timestruct;
- localtime_r(&msg2do->time, &timestruct);
- strftime(timestring, 64, DC_I18N_MSGTIMEF, &timestruct); /* recimo, da je 23 znakov */
- DC_SIMPLEPRINT(textwin, 1, "%.35s", msg2do->channel->name);
- getyx(textwin, y, x);
- wmove(textwin, y, 10);
- DC_SIMPLEPRINT(textwin, 2, " %.18s ", timestring);
- DC_SIMPLEPRINT(textwin, 4, "%.33s", msg2do->username);
- getyx(textwin, y, x);
- wmove(textwin, y, 37); /* quick mafs */
- DC_SIMPLEPRINT(textwin, 3, ": %s%s", msg2do->content ? msg2do->content : "", msg2do->attachment ? " " : "\n");
- if (msg2do->attachment)
- DC_SIMPLEPRINT(textwin, 8, "[" DC_I18N_ATTACHMENT "]: %s\n", msg2do->attachment);
- msg2do->status = 1;
- if (x); /* set but not used */
- return 1;
-}
-struct dc_ui {
- int maxy; /* max line of screen */
- int maxx; /* max column of screen */
+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;
};
-int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin, struct dc_ui ui) {
- struct dc_client * c = t->clients[0];
- /* first we trim spaces at the end */
- int i = 0, j = 0, k = 0, m = 0, n = 0;
- char * jp;
- for (i = strlen(l)-1; i >= 0; i--)
- if (l[i] == ' ')
- l[i] = '\0';
- else
+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 (l[0] == '/')
- switch (l[1]) {
- case 'g':
- case 'G':
- case 's':
- case 'S': /* servers */
- DC_CRLE(c, c->guilds_lock);
- for (i = 0; i < c->guilds_sizeof; i++)
- DC_SIMPLEPRINT(textwin, 4, " %02d. %s\n", i, c->guilds[i]->name);
- DC_CUE(c, c->guilds_lock);
- break;
- case 'c':
- case 'C':
- case 'k':
- case 'K':
- DC_CRLE(c, c->guilds_lock);
- if (!strchr(l, ' ') || (j = atoi(strchr(l, ' ')+1)) >= c->guilds_sizeof) {
- DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_CHANNELS_USAGE "\n", c->guilds_sizeof-1);
- DC_CUE(c, c->guilds_lock);
- break;
- }
- for (i = 0; i < c->guilds[j]->channels_sizeof; i += 2) {
- int y = 0;
- int x = 0;
- getyx(textwin, y, x);
- DC_SIMPLEPRINT(textwin, 4, " %02d. %s - %s", i, c->guilds[j]->channels[i]->name, c->guilds[j]->channels[i]->topic);
- if (c->guilds[j]->channels_sizeof-1 == i) {
- DC_SIMPLEPRINT(textwin, 1, "\n");
- break;
- }
- wmove(textwin, y, ui.maxx/2);
- if (x); /* prevent unused warnings */
- DC_SIMPLEPRINT(textwin, 7, " %02d. %s - %s\n", i+1, c->guilds[j]->channels[i+1]->name, c->guilds[j]->channels[i+1]->topic);
- }
- DC_CUE(c, c->guilds_lock);
- break;
- case 'j':
- case 'J':
- case 'p':
- case 'P':
- DC_CWLE(c, c->guilds_lock);
-#define DC_UI_PL_GC() /* get guild and channel. ONLY USE IN THE CONTEXT (switch statement case) OF dc_ui_processline !!! */ \
- if (!(jp = strchr(l, ' ')) || (j = atoi(jp+1)) >= c->guilds_sizeof) { \
- DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_GC_USAGE "\n", c->guilds_sizeof-1, 999); \
- DC_CUE(c, c->guilds_lock); \
- break; \
- } \
- if (!strchr(jp+1, ' ') || (k = atoi(strchr(jp+1, ' ')+1)) >= c->guilds[j]->channels_sizeof) { \
- DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_GC_USAGE "\n", c->guilds_sizeof-1, c->guilds[j]->channels_sizeof-1); \
- DC_CUE(c, c->guilds_lock); \
- break; \
- }
- DC_UI_PL_GC();
- for (i = c->guilds[j]->channels[k]->messages_sizeof-1; i >= 0; i--)
- dc_ui_print_message(textwin, c->guilds[j]->channels[k]->messages[i]);
- for (m = 0; m < c->guilds_sizeof; m++) /* we loop over all channels */
- for (n = 0; n < c->guilds[m]->channels_sizeof; n++)
- c->guilds[m]->channels[n]->focused = 0; /* remove focus from all channels */
- c->guilds[j]->channels[k]->focused = 1;
- c->guilds[j]->channels[k]->joined = 1;
- DC_CUE(c, c->guilds_lock);
- break;
- case 'l':
- case 'L':
- case 'z': /* leave */
- case 'Z':
- DC_CWLE(c, c->guilds_lock);
- DC_UI_PL_GC();
- c->guilds[j]->channels[k]->focused = 0;
- c->guilds[j]->channels[k]->joined = 0;
- DC_CUE(c, c->guilds_lock);
- break;
- case 'q':
- case 'Q':
- case 'i':
- case 'I':
- t->power_api = 2; /* 2 for shutdown */
- t->power_ui = 2;
- break;
- case 'N': /* api nit (thread) control */
- case 'n':
- if (!strchr(l, ' ')) {
- DC_SIMPLEPRINT(textwin, 1, "!strchr(l, ' ')\n");
- break;
- }
- t->power_api = atoi(strchr(l, ' ')+1);
- DC_SIMPLEPRINT(textwin, 4, "t->power_api = %d\n", atoi(strchr(l, ' ')+1));
- break;
- default:
- DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_CNF "\n");
- }
- else { /* send the message, it's not a command */
- struct dc_channel * ch = NULL;
- for (int m = 0; m < c->guilds_sizeof; m++) /* we loop over all channels */
- for (int n = 0; n < c->guilds[m]->channels_sizeof; n++)
- if (c->guilds[m]->channels[n]->focused)
- ch = c->guilds[m]->channels[n];
- /* DC_SIMPLEPRINT(textwin, 2, "%s - %s\n", ch->name, ch->guild->name); */ /* debug */
- if (!ch) {
- DC_SIMPLEPRINT(textwin, 1, "!ch - %s\n", DC_I18N_UI_NOT_JOINED);
- return -1;
- }
- if (!strlen(l)) {
- DC_SIMPLEPRINT(textwin, 1, "!strlen(l) - %s\n", DC_I18N_UI_EMPTYMSG);
- return -2;
}
- if (time(NULL) - c->last_sent_message <= ch->slowmode) {
- DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_SLOWMODE "\n", ch->slowmode, ch->slowmode-(time(NULL)-c->last_sent_message));
- return -3;
+ 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;
}
- c->last_sent_message = time(NULL); /* because the other thread may not update counter before the next message is sent */
- /* raise(SIGINT); */ /* To continue from here in GDB: "signal 0". */
- DC_CWLE(c, c->sent_messages_lock);
- c->sent_messages = realloc(c->sent_messages, sizeof(struct dc_message *)*++c->sent_messages_sizeof);
-#define DC_UISM c->sent_messages[c->sent_messages_sizeof-1] /* ui send messaeg */
- DC_UISM = calloc(1, sizeof(struct dc_message));
- DC_UISM->content = malloc(strlen(l)+1);
- strcpy(DC_UISM->content, l);
- DC_UISM->channel = ch;
- DC_CUE(c, c->sent_messages_lock);
- /* DO NOT free it */
}
- wrefresh(textwin);
- return 1;
+ 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);
}
-int dc_ui_thread (struct dc_thread_control * t) {
- while (!t->power_ui) usleep(250000);
- FIELD * field[2]; /* field[0] je polje z besedilom */
- field[1] = NULL;
- FORM * form;
- int ret, x, y;
- wint_t ch;
- struct dc_client * c = t->clients[0];
- struct dc_ui ui;
- initscr();
- cbreak();
- noecho();
- nodelay(stdscr, TRUE);
- setlocale(LC_ALL, "");
- start_color();
- init_pair(2, COLOR_YELLOW, COLOR_BLACK);
- init_pair(1, COLOR_RED, COLOR_BLACK);
- init_pair(3, COLOR_WHITE, COLOR_BLACK);
- init_pair(4, COLOR_GREEN, COLOR_BLACK);
- init_pair(5, COLOR_CYAN, COLOR_BLACK);
- init_pair(6, COLOR_BLACK, COLOR_CYAN);
- init_pair(7, COLOR_MAGENTA, COLOR_BLACK);
- init_pair(8, COLOR_BLUE, COLOR_WHITE);
- keypad(stdscr, TRUE);
- getmaxyx(stdscr, y, x); /* to je macro, zato y in x nista kazalca (;: */
- ui.maxy = y;
- ui.maxx = x;
- WINDOW * textwin = subwin(stdscr, y-3, x, 0, 0);
- WINDOW * formwin = subwin(stdscr, 2, x, y-2, 0);
- scrollok(textwin, TRUE);
- field[0] = new_field(2, x, y-2, 0, 5 /* offscreen rows */, 0);
- set_field_back(field[0], A_UNDERLINE);
- field_opts_off(field[0], O_AUTOSKIP);
- form = new_form(field);
- set_form_win(form, formwin);
- post_form(form);
- int i = 0;
- int updinforow = 1;
- wmove(textwin, 0, 0);
- refresh();
- while (t->power_ui != 2) {
- if (!(rand() % 10)) { /* roughly every 10 cycles we get errors and messages */
- assert(!DC_CRLE(c, c->errors_lock));
- for (int i = 0; i < c->errors_sizeof; i++) {
- if (!c->errors[i]->reported) {
- DC_SIMPLEPRINT(textwin, 1, "[" DC_I18N_ERROR "] %s()@%s:%lu: %s\n", c->errors[i]->function, c->errors[i]->file, c->errors[i]->line, c->errors[i]->message);
- c->errors[i]->reported = 1;
- }
- }
- assert(!pthread_rwlock_unlock(c->errors_lock)); /* deadlock if we unlock errors with error reporting, duh */
- if (c->newmessages || !(rand() % 10)) { /* a race happens */ /* so we add the random every 10*10=100 cycles */
- c->newmessages = 0; /* here inbetween */
- DC_CRLE(c, c->guilds_lock);
- for (int m = 0; m < c->guilds_sizeof; m++) /* we loop over all channels */
- for (int n = 0; n < c->guilds[m]->channels_sizeof; n++) {
- if (!c->guilds[m]->channels[n]->joined)
- continue;
- for (int i = c->guilds[m]->channels[n]->messages_sizeof-1; i >= 0; i--) {
- struct dc_message * msg2do = c->guilds[m]->channels[n]->messages[i];
- if (!msg2do->status)
- dc_ui_print_message(textwin, msg2do);
- }
- }
- DC_CUE(c, c->guilds_lock);
- }
- if (updinforow) {
- updinforow = 0;
- curs_set(0); /* too flashy */
- attron(COLOR_PAIR(5));
- int drawn = 0;
- int k = 0;
- move(y-3, 0); clrtoeol(); /* clear line */
- DC_CRLE(c, c->guilds_lock);
- for (int m = 0; m < c->guilds_sizeof; m++) /* we loop over all channels */
- for (int n = 0; n < c->guilds[m]->channels_sizeof; n++) {
- if (!c->guilds[m]->channels[n]->joined)
- continue;
- k++;
- if (drawn + strlen(c->guilds[m]->channels[n]->name)+2 > x+1) {
- attron(COLOR_PAIR(3));
- for (int l = x-3; l <= x; l++)
- mvprintw(y-3, l, ".");
- break;
- }
- if (c->guilds[m]->channels[n]->focused)
- attron(COLOR_PAIR(6));
- else
- attron(COLOR_PAIR(5));
- mvprintw(y-3, drawn, "#%s(%02.2d %02.2d)", c->guilds[m]->channels[n]->name, m, n);
- drawn += strlen(c->guilds[m]->channels[n]->name)+9; /* plus 9: 8 are # and (00 00), the other is for the following space between channels */
- }
- DC_CUE(c, c->guilds_lock);
- if (!k) {
- attron(COLOR_PAIR(2));
- if (t->power_api == 0)
- mvprintw(y-3, 0, DC_I18N_UI_LINE_BEFORE_NETWORK);
- else
- mvprintw(y-3, 0, DC_I18N_UI_LINE_BEFORE_JOIN);
- }
- curs_set(1);
- }
- pos_form_cursor(form);
- }
- ret = get_wch(&ch);
- if (ret != ERR) {
- switch (ch) {
- case KEY_LEFT:
- form_driver(form, REQ_PREV_CHAR);
- break;
- case KEY_RIGHT:
- form_driver(form, REQ_NEXT_CHAR);
- break;
- case KEY_BACKSPACE:
- form_driver(form, REQ_DEL_PREV);
- break;
- case KEY_DOWN:
- form_driver(form, REQ_NEXT_LINE);
- break;
- case KEY_UP:
- form_driver(form, REQ_PREV_LINE);
- break;
- case KEY_DC:
- form_driver(form, REQ_DEL_CHAR);
- break;
- case KEY_END:
- form_driver(form, REQ_END_LINE);
- break;
- case KEY_HOME:
- form_driver(form, REQ_BEG_LINE);
- break;
- case KEY_SLEFT:
- form_driver(form, REQ_PREV_WORD);
- break;
- case KEY_SRIGHT:
- form_driver(form, REQ_NEXT_WORD);
- break;
- case KEY_SDC:
- form_driver(form, REQ_CLR_FIELD);
- break;
- /* case KEY_NPAGE:
- wscrl(textwin, 10);
- break;
- case KEY_PPAGE:
- wscrl(textwin, -10);
- break;
- */ /* you wish! ncurses does not keep scrollback. i could use fancy features such as pads, but I'll just make a gui instd */
- case 9: /* idk fucken keybd */
- case KEY_STAB: /* switch to next channel for sending */
- dc_null();
- int firstjoined_g = -1;
- int firstjoined_c = -1;
- int foundfocused = 0;
- DC_CWLE(c, c->guilds_lock);
- for (int m = 0; m < c->guilds_sizeof; m++) /* we loop over all channels */
- for (int n = 0; n < c->guilds[m]->channels_sizeof; n++) {
- if (firstjoined_g == -1 && c->guilds[m]->channels[n]->joined) {
- firstjoined_g = m;
- firstjoined_c = n;
- }
- if (!foundfocused && c->guilds[m]->channels[n]->focused) {
- c->guilds[m]->channels[n]->focused = 0;
- foundfocused = 1;
- continue;
- }
- if (foundfocused && c->guilds[m]->channels[n]->joined) {
- c->guilds[m]->channels[n]->focused = 1;
- goto found;
- break;
- }
- }
- if (firstjoined_g != -1)
- c->guilds[firstjoined_g]->channels[firstjoined_c]->focused = 1;
- found:
- DC_CUE(c, c->guilds_lock);
- updinforow++;
- break;
- case KEY_ENTER:
- case 10:
- form_driver(form, REQ_NEXT_FIELD);
- form_driver(form, REQ_PREV_FIELD);
- if (dc_ui_processline(t, field_buffer(field[0], 0), textwin, ui) > 0)
- form_driver(form, REQ_CLR_FIELD);
- pos_form_cursor(form);
- updinforow++;
- break;
- default:
- form_driver_w(form, ret, ch);
- break;
- }
- wrefresh(formwin);
- /* wrefresh(textwin); */
+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;
}
- i++;
- usleep(2500);
- while (t->power_ui == 0) usleep(250000);
+ else { /* send message to channel */
+
}
- unpost_form(form);
- free_form(form);
- free_field(field[0]);
- endwin();
- return 1;
+ /* 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;
}
diff --git a/src/ui.glade b/src/ui.glade
new file mode 100644
index 0000000..555a70a
--- /dev/null
+++ b/src/ui.glade
@@ -0,0 +1,822 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkListStore" id="dc_main_tree"/>
+ <object class="GtkTextBuffer" id="dc_message_input">
+ <signal name="changed" handler="dc_ui_inputbox_changed" swapped="no"/>
+ </object>
+ <object class="GtkWindow" id="dc_window_registration">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Registration</property>
+ <property name="icon_name">dialog-password</property>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_homogeneous">True</property>
+ <child>
+ <object class="GtkEntry" id="dc_registration_email">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="placeholder_text" translatable="yes">Email address</property>
+ <property name="input_purpose">email</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Email address</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Password (&gt;5 characters)</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dc_registration_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="placeholder_text" translatable="yes">Password</property>
+ <property name="input_purpose">password</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Reveal password</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="dc_registration_reveal">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="state-set" handler="dc_ui_reveal_password" object="dc_registration_password" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Username (&gt;1 character)</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dc_registration_username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="placeholder_text" translatable="yes">Random #%04.4d'll be appended</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dc_registration_response">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">After clicking OK, this is where the response will be displayed.</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkButton" id="dc_registration_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="dc_ui_handle_close" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="dc_registration_ok">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Date of birth will be randomly generated.</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkWindow" id="dc_window_settings">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Settings</property>
+ <property name="icon_name">emblem-system</property>
+ <signal name="delete-event" handler="dc_ui_handle_close" swapped="no"/>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_homogeneous">True</property>
+ <child>
+ <object class="GtkEntry" id="dc_settings_login">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="placeholder_text" translatable="yes">Email address</property>
+ <property name="input_purpose">email</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Login</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Password</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dc_settings_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="placeholder_text" translatable="yes">Password</property>
+ <property name="input_purpose">password</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Reveal password</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Use a multiline textbox
+(Send with Ctrl+Enter)</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="dc_settings_multiline">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="state-set" handler="dc_ui_set_multiline" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="dc_settings_reveal">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="state-set" handler="dc_ui_reveal_password" object="dc_settings_password" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="dc_settings_register">
+ <property name="label" translatable="yes">Create an account</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="dc_ui_spawn_window" object="dc_window_registration" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Configuration file: ~/.config/discord.c</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="dc_settings_ok">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="dc_ui_settings_ok" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="dc_settings_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="dc_ui_handle_close" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkTextBuffer" id="textbuffer4">
+ <property name="text" translatable="yes">This is a message that you had sent. You can edit it and it will update on the server.</property>
+ </object>
+ <object class="GtkApplicationWindow" id="dc_window_main">
+ <property name="can_focus">False</property>
+ <signal name="delete-event" handler="gtk_main_quit" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkToolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkToolButton" id="dc_main_settings">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+P</property>
+ <property name="label" translatable="yes">Settings</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-preferences</property>
+ <signal name="clicked" handler="dc_ui_spawn_window" object="dc_window_settings" swapped="no"/>
+ <accelerator key="p" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="dc_main_mic">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+M</property>
+ <property name="label" translatable="yes">Microphone</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">audio-input-microphone</property>
+ <accelerator key="m" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="dc_main_disconnect">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+D</property>
+ <property name="label" translatable="yes">Disconnect</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-disconnect</property>
+ <accelerator key="d" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="dc_main_camera">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+F</property>
+ <property name="label" translatable="yes">Camera</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">camera-video</property>
+ <accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="dc_main_share_screen">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+S</property>
+ <property name="label" translatable="yes">Share screen</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">video-display</property>
+ <accelerator key="s" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVolumeButton">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+L</property>
+ <property name="relief">none</property>
+ <property name="orientation">vertical</property>
+ <property name="icons">audio-volume-muted-symbolic
+audio-volume-high-symbolic
+audio-volume-low-symbolic
+audio-volume-medium-symbolic</property>
+ <child internal-child="plus_button">
+ <object class="GtkButton">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="relief">none</property>
+ </object>
+ </child>
+ <child internal-child="minus_button">
+ <object class="GtkButton">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="relief">none</property>
+ </object>
+ </child>
+ <accelerator key="l" signal="popup" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="dc_main_send">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+Enter</property>
+ <property name="label" translatable="yes">Send</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">mail-send</property>
+ <signal name="clicked" handler="dc_ui_inputbox_activate" swapped="no"/>
+ <accelerator key="Return" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkPaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTreeView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+G</property>
+ <property name="model">dc_main_tree</property>
+ <property name="headers_visible">False</property>
+ <property name="reorderable">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="sizing">autosize</property>
+ <property name="title" translatable="yes">column</property>
+ <property name="clickable">True</property>
+ </object>
+ </child>
+ <accelerator key="g" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="dc_main_scroll">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="dc_main_messages">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+T</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Else</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">16:21</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">You</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">16:22</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTextView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="buffer">textbuffer4</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Someone</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">16:20</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">This is a message that someone else had sent. You can't modify it. You can reply to the message by clicking on it.</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">This is a message that someone else had sent. You can't modify it. You can reply to the message by clicking on it.</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <accelerator key="t" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkEntry" id="dc_main_singleline">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+I to focus, Return to send.</property>
+ <property name="caps_lock_warning">False</property>
+ <property name="placeholder_text" translatable="yes">Enter message and send with Enter</property>
+ <signal name="activate" handler="dc_ui_inputbox_activate" swapped="no"/>
+ <signal name="changed" handler="dc_ui_inputbox_changed" swapped="no"/>
+ <accelerator key="i" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
+ <accelerator key="Return" signal="activate"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTextView" id="dc_main_multiline">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Ctrl+I to focus, Ctrl+Enter to send</property>
+ <property name="border_width">1</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="buffer">dc_message_input</property>
+ <accelerator key="i" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dc_main_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">discord.c status bar</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>