summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsijanec <anton@sijanec.eu>2021-03-17 23:21:37 +0100
committersijanec <anton@sijanec.eu>2021-03-17 23:21:37 +0100
commit2a5e31c42603f38e9933e0f0cc1857da618f98c5 (patch)
treed27a1278eb8a5fa9255ea33c16b7427779f3bf1d
parentkončal api, "končal" main, začel ui (diff)
downloaddiscord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar.gz
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar.bz2
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar.lz
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar.xz
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.tar.zst
discord.c-2a5e31c42603f38e9933e0f0cc1857da618f98c5.zip
-rw-r--r--Makefile4
-rw-r--r--misc/openssl.supp17
-rw-r--r--misc/valgrind-supp-extractor.c16
-rw-r--r--src/api.c242
-rw-r--r--src/i18n.h8
-rw-r--r--src/main.c53
-rw-r--r--src/ui.c188
7 files changed, 371 insertions, 157 deletions
diff --git a/Makefile b/Makefile
index de9b211..5d5f623 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,9 @@
default:
- gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread src/api.c -lcurl -lncurses -lform -odiscord.c
+ gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread src/main.c -lcurl -lncursesw -lformw -lm -odiscord.c
prepare:
wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.c -O lib/cJSON.c
wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.h -O lib/cJSON.h
clean:
rm lib/cJSON.c lib/cJSON.h
+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
diff --git a/misc/openssl.supp b/misc/openssl.supp
new file mode 100644
index 0000000..21d47cd
--- /dev/null
+++ b/misc/openssl.supp
@@ -0,0 +1,17 @@
+{
+ Ignore OpenSSL malloc
+ Memcheck:Leak
+ fun:malloc
+ fun:CRYPTO_malloc
+ ...
+ obj:*libcrypto*
+}
+
+{
+ Ignore OpenSSL realloc
+ Memcheck:Leak
+ fun:realloc
+ fun:CRYPTO_realloc
+ ...
+ obj:*libcrypto*
+}
diff --git a/misc/valgrind-supp-extractor.c b/misc/valgrind-supp-extractor.c
new file mode 100644
index 0000000..0b56a91
--- /dev/null
+++ b/misc/valgrind-supp-extractor.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+int main (int argc, char ** argv) {
+ char c = fgetc(stdin);
+ int s = 0; /* 0: not in supp, 1: in supp */
+ while (!feof(stdin)) {
+ if (c == '}')
+ s = 0;
+ if (s)
+ fputc(c, stdout);
+ if (c == '{')
+ s = 1;
+ c = fgetc(stdin);
+ }
+ fflush(stdout);
+ return 0;
+}
diff --git a/src/api.c b/src/api.c
index a90b8ac..f798aff 100644
--- a/src/api.c
+++ b/src/api.c
@@ -1,8 +1,4 @@
-#if __INCLUDE_LEVEL__ != 0
#pragma once
-#endif
-#define _XOPEN_SOURCE 600
-#define _POSIX_C_SOURCE 200809L
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
@@ -18,12 +14,14 @@
#include <printf.h>
#include <pthread.h>
#include <stdatomic.h>
+#include <math.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(e, s, l, m, ...) dc_push_error(e, s, l, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)
+/* #define DC_ERROR(e, s, l, m, ...) (dc_push_error(e, s, l, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)) */
+#define DC_ERROR(e, s, l, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__))
#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c->errors, &c->errors_sizeof, c->errors_lock, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */
-#define DC_API(curl, body, endpoint, ...) dc_api(curl, body, 0##__VA_OPT__(1), endpoint __VA_OPT__(,) __VA_ARGS__)
+#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_CWLE(c, name) (pthread_rwlock_wrlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0)
@@ -159,26 +157,19 @@ struct dc_client {
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;
-#define DC_CILI(name) /* Client Init Lock Init */ if (pthread_rwlock_init(name##_lock, NULL)) goto fuck;
+#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;
- fuck:
-#define DC_CILD(name) if (name##_lock) pthread_rwlock_destroy(name##_lock)
- DC_CILD(c->authorization);
- DC_CILD(c->username);
- DC_CILD(c->guilds);
- DC_CILD(c->errors);
- DC_CILD(c->sent_messages);
- free(c);
- return NULL; /* don't even check for this, if pthread_rwlock_init fails we'll segfault */
}
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;
@@ -197,6 +188,12 @@ void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only calle
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_error ** e, _Atomic size_t * s, pthread_rwlock_t * lock, const char * c, char * f, size_t l, unsigned short int isfmt, char * m, ...) {
@@ -228,38 +225,90 @@ int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t *
return -2;
return 1;
}
-cJSON * dc_api (CURL * curl, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */
- if (!curl)
+cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */
+ if (!c)
return NULL;
- if (!endpoint)
+ 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;
- init_writefunc_string(&s);
+ struct writefunc_string s, h;
size_t va_count = parse_printf_format(endpoint, 0, NULL);
char * endpoint_formatted = NULL;
- if (isfmt && va_count > 0) {
+ 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, endpoint, ap2); /* sn instead of s because double evaulation may produce */
+ 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(curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint);
+ curl_easy_setopt(c->curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint);
if (!body)
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
- else
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
- if (curl_easy_perform(curl) != CURLE_OK)
+ curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
+ 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 */
+ curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
+ curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, &h);
+ curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, 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);
+ }
+ fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h.ptr, s.ptr);
+ fflush(netreq);
+ char * cp = strstr(h.ptr, "\nx-ratelimit-reset-after: ");
+ if (cp == NULL)
+ goto norlheaders;
+ cp += strlen("\nx-ratelimit-reset-after: ");
+ double retry_after = strtod(cp, NULL);
+ cp = strstr(h.ptr, "\nx-ratelimit-remaining: ");
+ if (cp == NULL)
+ goto norlheaders;
+ cp += strlen("\nx-ratelimit-remaining: ");
+ if (cp[0] >= '0' && cp[0] <= '9' && atoi(cp) == 0) { /* X-RateLimit-Remaining: 0 */
+ DC_CLIENT_ERROR(c, DC_I18N_HITRL " %lfs. endpoint = %s", retry_after, endpoint_formatted ? endpoint_formatted : endpoint);
+ sleep(ceil(retry_after)+1); /* TODO: prevent hanging entire thread just for this */
+ free(s.ptr); s.ptr = NULL;
+ free(h.ptr); h.ptr = NULL;
+ cJSON_Delete(json);
+ json = NULL;
+ if (retried++)
+ return NULL;
+ goto retry;
+ }
+ norlheaders:
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;
+ }
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;
return json;
}
int dc_login (struct dc_client * c) { /* noui */
@@ -280,9 +329,9 @@ int dc_login (struct dc_client * c) { /* noui */
return -3;
}
init_writefunc_string(&s);
- data = malloc(strlen(DC_LOGIN_FORMAT)+strlen(c->email)+strlen(c->password)+1);
- CURLcode res;
+ data = malloc(snprintf(data, 0, DC_LOGIN_FORMAT, c->email, c->password)+1);
sprintf(data, DC_LOGIN_FORMAT, c->email, c->password);
+ CURLcode res;
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);
@@ -301,7 +350,7 @@ int dc_login (struct dc_client * c) { /* noui */
json = cJSON_Parse(s.ptr);
if (!json) {
const char *error_ptr = cJSON_GetErrorPtr();
- if (!error_ptr) {
+ if (error_ptr) {
DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
} else {
DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
@@ -318,14 +367,15 @@ int dc_login (struct dc_client * c) { /* noui */
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);
- strcpy(data, "Authorization: ");
+ 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;}
free(s.ptr); s.ptr = NULL;
init_writefunc_string(&s);
curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me");
curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
c->curl_headers = curl_slist_append(c->curl_headers, data);
res = curl_easy_perform(c->curl);
@@ -338,7 +388,7 @@ int dc_login (struct dc_client * c) { /* noui */
json = cJSON_Parse(s.ptr);
if (!json) {
const char *error_ptr = cJSON_GetErrorPtr();
- if (!error_ptr) {
+ if (error_ptr) {
DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
} else {
DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
@@ -368,10 +418,8 @@ int dc_fetch_guilds (struct dc_client * c) {
if (!c)
return -5;
int rs = 1;
- struct writefunc_string s;
char * value = NULL;
char * value2 = NULL;
- CURLcode res;
cJSON * json = NULL;
if (c->discriminator < 0) {
if ((rs = dc_login(c)) < 0) {
@@ -379,24 +427,9 @@ int dc_fetch_guilds (struct dc_client * c) {
return -1;
} else rs = 1;
}
- curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me/guilds");
- curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
- curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
- init_writefunc_string(&s);
- res = curl_easy_perform(c->curl);
- if (res != CURLE_OK) {
- DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res));
- rs = -2;
- goto rc;
- }
- json = cJSON_Parse(s.ptr);
+ json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me/guilds");
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 ": %s", error_ptr);
- } else {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
- }
+ DC_CLIENT_ERROR(c, "DC_CAPI users/@me/guilds " DC_I18N_FAILED);
rs = -3;
goto rc;
}
@@ -408,10 +441,12 @@ int dc_fetch_guilds (struct dc_client * c) {
if(DC_CWLE(c, c->guilds_lock)) {rs = -5; goto rc;}
cJSON * guild = NULL;
cJSON_ArrayForEach(guild, json) {
- value = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
- value2 = cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"));
+ value = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "name"));
+ value2 = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "id"));
if (!value || !value2) {
- DC_CLIENT_ERROR(c, "!cJSON_GetStringValue(cJSON_GetObjectItem(json, \"id\" || \"name\"))");
+ 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);
@@ -419,6 +454,7 @@ int dc_fetch_guilds (struct dc_client * c) {
if (idull == c->guilds[i]->id)
continue; /* remove duplicates */
c->guilds = realloc(c->guilds, sizeof(struct dc_guild *)*++c->guilds_sizeof);
+ c->guilds[c->guilds_sizeof-1] = malloc(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;
@@ -428,7 +464,6 @@ int dc_fetch_guilds (struct dc_client * c) {
}
if(DC_CUE(c, c->guilds_lock)) {rs = -6; goto rc;}
rc:
- free(s.ptr); s.ptr = NULL;
cJSON_Delete(json); json = NULL;
return rs;
}
@@ -446,34 +481,17 @@ int dc_fetch_channels (struct dc_guild * g) {
return -3;
} else rs = 1;
}
- CURLcode res;
- struct writefunc_string s;
- init_writefunc_string(&s);
cJSON * json = NULL;
- char * url = malloc(strlen(DC_API_PREFIX "guilds/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/channels")+1);
- sprintf(url, DC_API_PREFIX "guilds/%llu/channels", g->id); /* only api thread writes ids, so this is safe without locks */
- curl_easy_setopt(c->curl, CURLOPT_URL, url);
- curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
- curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
- res = curl_easy_perform(c->curl);
- if (res != CURLE_OK) {
- DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res));
- rs = -4;
- goto rc;
- }
- json = cJSON_Parse(s.ptr);
+ json = DC_CAPI(c, NULL, DC_API_PREFIX "guilds/%llu/channels", g->id); /* ids are only written by api thread, nolock */
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 ": %s", error_ptr);
- } else {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
- }
+ DC_CLIENT_ERROR(c, "DC_CAPI guilds/%llu/channels " DC_I18N_FAILED, g->id);
rs = -5;
goto rc;
}
if (!cJSON_IsArray(json)) {
- DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), s.ptr = %s", s.ptr);
+ char * jsonstr = cJSON_Print(json);
+ DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), g->id = %llu, json = %s", g->id, jsonstr);
+ free(jsonstr);
rs = -6;
goto rc;
}
@@ -481,10 +499,10 @@ int dc_fetch_channels (struct dc_guild * g) {
/* 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) {
- char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(json, "topic"));
- char * name = cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"));
- char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
- double type = cJSON_GetNumberValue(cJSON_GetObjectItem(json, "type"));
+ char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "topic"));
+ char * name = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "name"));
+ char * id = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "id"));
+ double type = cJSON_GetNumberValue(cJSON_GetObjectItem(channel, "type"));
if (!id || !name || type == NAN) {
DC_CLIENT_ERROR(c, "!id || !name || type == NAN");
continue;
@@ -498,6 +516,7 @@ int dc_fetch_channels (struct dc_guild * g) {
if (idull == g->channels[i]->id)
continue; /* remove duplicates */
g->channels = realloc(g->channels, sizeof(struct dc_channel *)*++g->channels_sizeof);
+ g->channels[g->channels_sizeof-1] = malloc(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);
@@ -509,8 +528,6 @@ int dc_fetch_channels (struct dc_guild * g) {
}
if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;}
rc:
- free(s.ptr); s.ptr = NULL;
- free(url); url = NULL;
cJSON_Delete(json); json = NULL;
return rs;
}
@@ -542,14 +559,9 @@ int dc_send_message (struct dc_message * m) { /* nolock - once message is append
}
cJSON_Delete(json); json = NULL;
/* {content: "yeet", nonce: "820762917392613376", tts: false} */
- json = DC_API(c->curl, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id); /* ids are only written by api thread, nolock */
+ json = DC_CAPI(c, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id); /* ids are only written by api thread, nolock */
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 ": %s", error_ptr);
- } else {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
- }
+ DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages " DC_I18N_FAILED, m->channel->id);
rs = -5;
goto rc;
}
@@ -590,14 +602,9 @@ int dc_fetch_messages (struct dc_channel * ch) {
return -4;
} else rs = 1;
}
- cJSON * json = DC_API(c->curl, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100&_=%d", ch->id, rand());
+ cJSON * json = DC_CAPI(c, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100&_=%d", ch->id, rand());
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 ": %s", error_ptr);
- } else {
- DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
- }
+ DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages?limit=100&_=***rand()*** " DC_I18N_FAILED, ch->id);
rs = -5;
goto rc;
}
@@ -609,11 +616,11 @@ int dc_fetch_messages (struct dc_channel * ch) {
if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} /* we lock all guilds of a client when writing messages */
cJSON * message = NULL;
cJSON_ArrayForEach(message, json) {
- char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(json, "timestamp"));
- char * content = cJSON_GetStringValue(cJSON_GetObjectItem(json, "content"));
- char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
- char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "discriminator"));
- char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "username"));
+ 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"));
if (!id || !timestamp || !content || !username || !discriminator) {
DC_CLIENT_ERROR(c, "!id || !timestamp || !content || !username || discriminator < 0");
continue;
@@ -632,8 +639,9 @@ int dc_fetch_messages (struct dc_channel * ch) {
for (int i = 0; i < ch->messages_sizeof; i++)
if (idull == ch->messages[i]->id)
continue; /* remove duplicates */
- ch->messages = realloc(ch->messages, sizeof(struct dc_message *)**+ch->messages_sizeof);
+ 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_FMTM = malloc(sizeof(struct dc_message));
DC_FMTM->time = mktime(&tm);
DC_FMTM->content = malloc(strlen(content)+1);
strcpy(DC_FMTM->content, content);
@@ -650,8 +658,9 @@ int dc_fetch_messages (struct dc_channel * ch) {
return rs;
}
struct dc_thread_control {
- unsigned short int power_api; /* 1 if the thread should run, set it to 0 for the thread to return at the end of the loop */
+ 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*/
@@ -660,38 +669,41 @@ struct dc_thread_control {
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 */
/* if (pthread_rwlock_wrlock(t->clients_lock))
return -1; */ /* clients are not locked yet */
- for (int i = 0; i < t->clients_sizeof; i++)
+ for (int i = 0; i < t->clients_sizeof && t->power_api != 2; i++)
dc_login(t->clients[i]);
- while (t->power_api == 1) { /* as there's only one api thread and only it modifies things that need a guilds_lock, it's okay to */
+ 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 || !(rand() % 1000)) /* roughly every 1000 cycles we'll update guilds */
+ if (!t->clients[i]->guilds_sizeof /* || !(rand() % 1000) */) /* roughly every 1000+inf cycles we'll update guilds */
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 cycles we'll update channels */
+ 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[j]->channels_sizeof; k++)
if (!(rand() % 10)) /* roughly every 10 cycles we'll update messages */
dc_fetch_messages(t->clients[i]->guilds[j]->channels[k]);
}
if (DC_CWLE(t->clients[i], t->clients[i]->sent_messages_lock)) continue;
- if (t->clients[i]->sent_messages_sizeof > 0) {/* no need to lock as only one thread does this */
+ 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);
+ /* 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);
+ 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; 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--;
}
}
- while (DC_CUE(t->clients[i], t->clients[i]->sent_messages_lock));
+ 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);
}
/* if (pthread_rwlock_unlock(t->clients_lock))
return -2; */
diff --git a/src/i18n.h b/src/i18n.h
index 81e7d70..7cf244f 100644
--- a/src/i18n.h
+++ b/src/i18n.h
@@ -12,3 +12,11 @@
#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 "%a, %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_JOIN_USAGE "/pridruži <š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"
diff --git a/src/main.c b/src/main.c
index 28b1449..d9ec103 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,26 +1,31 @@
-#include <ui.h>
+#define _XOPEN_SOURCE 600
+#include <stdio.h>
+FILE * netreq;
+#include <ui.c>
int main (int argc, char ** argv) {
+ curl_global_init(CURL_GLOBAL_ALL);
srand(time(NULL));
+ netreq = fopen("netreq.log", "w");
int rs = 0;
int opt;
pthread_t api_thread, ui_thread;
- int api_ret ui_ret;
+ int api_ret, ui_ret;
struct dc_client * c = dc_client_init();
- struct dc_thread_control = {
- .power_api = 1;
- .power_ui = 1;
- .clients = &c;
- .clients_sizeof = 1;
- .cout = stdout;
- .cin = stdin;
- .cerr = stderr;
+ 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);
- dc_client_free(c);
- return 0;
+ fprintf(stdout, DC_I18N_USAGE, argv[0]);
+ rs = 4;
+ goto rc;
break;
case 'e':
c->email = malloc(strlen(optarg)+1);
@@ -33,14 +38,15 @@ int main (int argc, char ** argv) {
default:
fprintf(stderr, DC_I18N_UNREC_ARG "\n", opt);
dc_client_free(c);
- return 1;
+ rs = 1;
+ goto rc;
}
}
if (!c->email) {
if (!getenv("DC_E")) {
fprintf(stderr, DC_I18N_MISSING_EP "\n");
- dc_client_free(c);
- return 2;
+ rs = 2;
+ goto rc;
}
c->email = malloc(strlen(getenv("DC_E"))+1);
strcpy(c->email, getenv("DC_E"));
@@ -48,16 +54,23 @@ int main (int argc, char ** argv) {
if (!c->password) {
if (!getenv("DC_P")) {
fprintf(stderr, DC_I18N_MISSING_EP "\n");
- dc_client_free(c);
- return 3;
+ rs = 3;
+ goto rc;
}
c->password = malloc(strlen(getenv("DC_P"))+1);
strcpy(c->password, getenv("DC_P"));
}
- api_ret = pthread_create(&api_thread NULL, dc_api_thread, dc_thread_control);
- ui_ret = pthread_create(&ui_thread, NULL, dc_ui_thread, dc_thread_control);
+#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:
+ fclose(netreq);
+ dc_client_free(c);
+ if (api_ret || ui_ret); /* to hide warnings */
+ curl_global_cleanup();
return rs;
}
diff --git a/src/ui.c b/src/ui.c
index 3f8dbaf..2578689 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -1,34 +1,123 @@
-#define _XOPEN_SOURCE 600
-#define _POSIX_C_SOURCE 200809L
+#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
-/* #include <api.c> */
+#include <api.c>
#include <ncursesw/ncurses.h>
#include <ncursesw/form.h>
#include <unistd.h>
#include <locale.h>
+#include <string.h>
+#include <assert.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 */
-/* int dc_ui_thread (struct * dc_thread_control t) { */
-int main () {
+int dc_ui_print_message (WINDOW * textwin, struct dc_message * msg2do) {
+ 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, 3, "%023.23s %08.8s: %s\n", timestring, msg2do->username, msg2do->content);
+ return 1;
+}
+int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) {
+ struct dc_client * c = t->clients[0];
+ /* first we trim spaces at the end */
+ int i, j, k;
+ for (i = strlen(l)-1; i >= 0; i--)
+ if (l[i] == ' ')
+ l[i] = '\0';
+ else
+ 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++)
+ DC_SIMPLEPRINT(textwin, 4, " %02d. %s - %s\n", i, c->guilds[j]->channels[i]->name, c->guilds[j]->channels[i]->topic);
+ DC_CUE(c, c->guilds_lock);
+ break;
+ case 'j':
+ case 'J':
+ case 'p':
+ case 'P':
+ DC_CRLE(c, c->guilds_lock);
+ char * jp;
+ if (!(jp = strchr(l, ' ')) || (j = atoi(jp+1)) >= c->guilds_sizeof) {
+ DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_JOIN_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_JOIN_USAGE "\n", c->guilds_sizeof-1, c->guilds[j]->channels_sizeof-1);
+ DC_CUE(c, c->guilds_lock);
+ break;
+ }
+ for (i = 0; i < c->guilds[j]->channels[k]->messages_sizeof; i++)
+ dc_ui_print_message(textwin, c->guilds[j]->channels[k]->messages[i]);
+ c->joinedchannel = c->guilds[j]->channels[k];
+ 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));
+ default:
+ DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_CNF "\n");
+ }
+ wrefresh(textwin);
+ return 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];
initscr();
cbreak();
noecho();
nodelay(stdscr, TRUE);
setlocale(LC_ALL, "sl_SI.UTF-8");
start_color();
- init_pair(0, COLOR_WHITE, COLOR_BLACK);
+ init_pair(2, COLOR_YELLOW, COLOR_BLACK);
init_pair(1, COLOR_RED, COLOR_BLACK);
- attron(COLOR_PAIR(1));
+ init_pair(3, COLOR_WHITE, COLOR_BLACK);
+ init_pair(4, COLOR_GREEN, COLOR_BLACK);
keypad(stdscr, TRUE);
getmaxyx(stdscr, y, x); /* to je macro, zato y in x nista kazalca (;: */
- WINDOW * textwin = newwin(y-3, x, 0, 0);
- WINDOW * formwin = newwin(2, x, y-2, 0);
+ 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);
@@ -38,15 +127,43 @@ int main () {
post_form(form);
int i = 0;
wmove(textwin, 0, 0);
- time_t last_time = 0;
refresh();
- while (1) {
- if (last_time < time(NULL)) {
- last_time = time(NULL);
- attron(COLOR_PAIR(0));
- wprintw(textwin, "\nvrstica stevilka %d", i);
- attron(COLOR_PAIR(1));
- mvprintw(y-3, 0, "#piš-ti-kurc-2 | discord.c | šijanec 2021");
+ struct dc_channel * prev_joinedchannel = (void *)&i; /* just so we get something that isn't null without warnings (): */
+ while (t->power_ui != 2) {
+ if (!(rand() % 100)) { /* roughly every 100 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", 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->joinedchannel) {
+ DC_CRLE(c, c->guilds_lock);
+ for (int i = 0; i < c->joinedchannel->messages_sizeof; i++) {
+ struct dc_message * msg2do = c->joinedchannel->messages[i];
+ if (!msg2do->status) {
+ dc_ui_print_message(textwin, msg2do);
+ msg2do->status = 1;
+ }
+ }
+ DC_CUE(c, c->guilds_lock);
+ }
+ if (prev_joinedchannel != c->joinedchannel) {
+ curs_set(0); /* too flashy */
+ attron(COLOR_PAIR(2));
+ if (c->joinedchannel) {
+ DC_CRLE(c, c->guilds_lock);
+ mvprintw(y-3, 0, "#%s @ %s | %s", c->joinedchannel->name, c->joinedchannel->guild->name, c->joinedchannel->topic);
+ DC_CUE(c, c->guilds_lock);
+ }
+ else
+ mvprintw(y-3, 0, DC_I18N_UI_LINE_BEFORE_JOIN);
+ curs_set(1);
+ prev_joinedchannel = c->joinedchannel;
+ }
+ pos_form_cursor(form);
}
ret = get_wch(&ch);
if (ret != ERR) {
@@ -61,20 +178,49 @@ int main () {
form_driver(form, REQ_DEL_PREV);
break;
case KEY_DOWN:
- form_driver(form, REQ_SCR_FLINE);
+ form_driver(form, REQ_NEXT_LINE);
break;
case KEY_UP:
- form_driver(form, REQ_SCR_BLINE);
+ 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_ENTER:
+ case 10:
+ form_driver(form, REQ_NEXT_FIELD);
+ form_driver(form, REQ_PREV_FIELD);
+ dc_ui_processline(t, field_buffer(field[0], 0), textwin);
+ form_driver(form, REQ_CLR_FIELD);
+ pos_form_cursor(form);
break;
default:
form_driver_w(form, ret, ch);
break;
}
+ wrefresh(formwin);
}
- wnoutrefresh(stdscr);
+ /* wnoutrefresh(stdscr);
wnoutrefresh(textwin);
- doupdate();
+ doupdate(); */
i++;
+ usleep(2500);
+ while (t->power_ui == 0) usleep(250000);
}
unpost_form(form);
free_form(form);