diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api.c | 178 | ||||
-rw-r--r-- | src/h.c | 362 | ||||
-rw-r--r-- | src/lib.c | 12 | ||||
-rw-r--r-- | src/main.c | 19 | ||||
-rw-r--r-- | src/ui.c | 234 | ||||
-rw-r--r-- | src/ui.glade | 760 |
6 files changed, 1003 insertions, 562 deletions
@@ -0,0 +1,178 @@ +#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_SERVER_ADDRESS "localhost" +#define DC_API_PREFIX "/api/v9/" +#define DC_LWS_ABLE_TO_PARSE_HEADERS 1 +/* libwebsockets information: libwebsockets works with event loops and discord.c primary targets debian for building (it must work on debian), but debian does not ship libwebsockets with glib or libevent event loop support, so loops are implemented in the classic poll() style. this reduces performance probably. if you use discord.c API with support for a platform specific event loop, you may rewrite LWS to do things differently. currently calls into API (dc_api_i and dc_api_o) will both do LWS stuff. */ +void dc_api_stack (struct dc_api_io i) { /* stack output struct to be delivered via dc_api_o 2usr */ + DC_MR(i.program->api_ios); + i.program->api_ios[i.program->api_ios_length] = malloc(sizeof(i)); + *(i.program->api_ios[i.program->api_ios_length++]) = i; + return; +} +static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us, void * in, size_t len) { + struct dc_lws_pass * pass = (struct dc_lws_pass *) us; + switch (rs) { + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: /* TODO: handle and report somehow */ + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *) in : "(null)"); + break; + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: /* lws gives us len space after *in */ + unsigned char ** p = (unsigned char **) in, * end = (*p)+len; + for (int i = 0; i < DC_LWS_HEADERS_LENGTH; i++) + if (pass.headers[i][0]) + if (lws_add_http_header_by_name(wsi, (const unsigned char *) dc_lws_headers[i], (const unsigned char *) pass.headers[i], strlen(pass.headers[i]), p, end)) + return -1; + if (!lws_http_is_redirected_to_get(wsi)) { + lwsl_user("doing POST flow\n"); + lws_client_http_body_pending(wsi, 1); + lws_callback_on_writable(wsi); + } else + lwsl_user("doing GET flow, got redirected - this is a bug, report!\n"); + break; + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: /* see minimal post example on how to send */ + if (lws_http_is_redirected_to_get(wsi)) /* multipart with files, for example */ + break; /* for setting avatar pictures or uploading files. Yecch */ + lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITABLE"); + if (!(pass.api_io.status & DC_BODY_SENT)) { + if (lws_write(wsi, (uint8_t *) pass.body, pass.body_length, LWS_WRITE_HTTP) != (int) pass.body_length) /* TODO: fix to allow sending large bodies */ + return 1; /* we send body in one whole chunk OR FAIL! */ + pass.api_io.status |= DC_BODY_SENT; + lws_callback_on_writable(wsi); + } else { + if (lws_write(wsi, NULL, 0, LWS_WRITE_HTTP_FINAL) == -1 /* ret snt by */) + return 1; /* _FINAL is necessary to support H2, srv need know */ + lws_client_http_body_pending(wsi, 0); + } + if (pass.api_io.status & DC_MUST_FREE) + free(body); + break; + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + pass.status = lws_http_client_http_response(wsi); /* 200 OK, 404 Not Found, ...*/ + fprintf(stderr, "Connected with server response: %d\n", pass.status); +#ifdef DC_LWS_ABLE_TO_PARSE_HEADERS + /* how to query custom headers (http 1.x only at the moment) */ + for (int i = 0; i < DC_LWS_HEADERS_LENGTH; i++) { + pass.headers[i][0] = '\0'; /* to clear any headers we requested with */ + int n = lws_hdr_custom_length(wsi, dc_lws_headers[i], strlen(dc_lws_headers[i])); + if (n < -1) + lwsl_notice("No header %s.\n", dc_lws_headers[i]); + else + if (lws_hdr_custom_copy(wsi, pass.headers[i], sizeof(DC_MAX_HEADER_LENGTH), dc_lws_headers[i], strlen(dc_lws_headers[i])) < 0) + lwsl_notice("Header %s too long.\n", dc_lws_headers[i]); + else + lwsl_notice("%s %s\n", dc_lws_headers[i], pass.headers[i]); + } +#endif + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: /* chunked body, without headers */ + lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int) len); + lwsl_hexdump_notice(in, len); /* for debugging purposes, kek */ + pass->body_length = len; + pass->body = in; + ((char *)in+1)[len] = '\0'; /* NULL terminating note0 src/h.c */ + if (pass->api_io.pass != pass) + fprintf(stderr, "[!!!] REPORT THIS BUG: pass->api_io.pass != pass"); + dc_api_i(pass.api_io); + return 0; /* don't pass to lws_callback_http_dummy */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: /* uninterpreted http content */ + ; /* we add 1 to buffer size for NULL terminanting, note0 src/h.c */ + char buffer[DC_LWS_BUF + LWS_PRE + 1 /* lws needs this many bytes b4 the ptr */]; + char * ptr = buffer + LWS_PRE; /* see, BEFORE the pointer - lower addr */ + int len2 /* DC_LWS_BUF again */ = sizeof(buffer) - LWS_PRE; + if (lws_http_client_read(wsi, &ptr, &len2) < 0) + return -1; + return 0; /* don't pass to lws_callback_http_dummy */ + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); + break; + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + lwsl_user("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); + break; + case LWS_CALLBACK_WSI_DESTROY: /* if I understand the docs correctly, this is allways */ + free(pass); /* called at the final moment when user pointer and wsi is still */ + default: /* accessible, so we can now free the struct that was passed in as a heap ptr */ + break; + } + return lws_callback_http_dummy(wsi, rs, us, in, len); +} +void dc_api_i (struct dc_api_io i) { /* this function does not call attached functions, only output does that */ + if (!i.program) + return; + if (i.program->lws_context) + lws_service(i.program->lws_context, 0); + switch (i.type) { + case DC_API_MESSAGE: + break; + case DC_API_CHANNEL: + break; + case DC_API_GUILD: + break; + case DC_API_LOGIN: /* library currently only works for a single client */ + if (!i.program->clients_length) /* already attempted login */ + DC_MR(i.program->clients); + i.program->clients[0] = i.client; + struct lws_client_connect_info info; + memset(&info, 0, sizeof(info)); + info.context = i.program->lws_context; + /* info.ssl_connection = LCCSCF_USE_SSL | LCCSCF_HTTP_MULTIPART_MIME; */ + info.port = 80; + info.address = DC_SERVER_ADDRESS; + info.path = DC_API_PREFIX "auth/login"; + info.host = info.address; + info.origin = info.address; + info.method = "POST"; + struct dc_lws_pass pass; /* for passing data to/from lws callback function */ + memset(&pass, 0, sizeof(pass)); + i.status |= DC_MUST_FREE; + pass.body_length = smprintf(&pass.body, DC_LOGIN_FORMAT, i.client->email, i.client->password); + i.type = DC_API_LOGIN_CB; + memcpy(&pass.api_io, &i, sizeof(i)) + info.userdata = malloc(sizeof(pass)); + memcpy(info.userdata, &pass, sizeof(pass)); +#ifdef DC_LWS_ABLE_TO_PARSE_HEADERS + info.alpn = "http/1.1"; +#endif + info.protocol = dc_lws_protocols[0].name; + if (i.client->authorization) { /* TODO: attempt cookie login without user/pass */ + ; + } + lws_client_connect_via_info(&info); + break; + case DC_API_LOGIN_CB: + fprintf(stderr, "DEBUG: %s\n", i.pass->body); + break; + case DC_API_REGISTER: + break; + case DC_API_STATUS: + break; + case DC_API_USER: + break; + case DC_API_ROLE: + break; + case DC_API_NONE: + break; + case DC_API_ATTACH: + if (i.attached_function->type == DC_API_NONE) + break; + DC_MR(i.program->attached_functions); /* makes room for at least one */ + break; + } +} +struct dc_api_io dc_api_o (struct dc_api_io i /* for ->program */) { + if (i.program->lws_context) + lws_service(i.program->lws_context, 0); + struct dc_api_io o = { + .type = DC_API_NONE, + .program = i.program + }; + /* dc_api_stack pop: */ + if (i.program->api_ios_length) { + o = *(i.program->api_ios[--i.program->api_ios_length]); + dc_api_io_free(i.program->api_ios[i.program->api_ios_length]); + } + for (size_t j = 0; j < i.program->attached_functions_length; j++) { /* call attached functions */ + if (i.program->attached_functions[j]->type == o.type) /* wow, it's possible to attach to NONE */ + i.program->attached_functions[j]->function(o, i.program->attached_functions[j]->user_data); + } + return o; +} @@ -0,0 +1,362 @@ +/* note: ISAs will (hopefully) no longer be used in my programs in favour of linked lists */ +/* ISAs: _sizeof means size of array, _length means usable/initialized members in array */ +#define DC_ISA(type, name) type ** name; size_t name##_sizeof; size_t name##_length /* in struct array */ +#define DC_ALLOC_CHUNK 1 +#define DC_REALLOC_K 1.5 +#define DC_BIGGER_ARRAY(name) do { /* unlike in previous programs, _BIGGER_ARRAY */ \ + name##_sizeof = ceil(name##_sizeof*DC_REALLOC_K); /* no longer initializes-that'd */ \ + name = realloc(name, sizeof(name[0])*name##_sizeof); /* prevent inserting noninited */ \ + } while (0) /* note that sizeof(name[0]) does not dereferencec name */ +#define DC_MR(n) if (n##_sizeof <= n##_length) /* make room */ \ + DC_BIGGER_ARRAY(n) +#define DC_STRUCT_PREFIX void * data; /* some user data to be kind to api library users */ +#define DC_LWS_BUF (2^14) /* 16384 byte chunks are handled at a time when RXing HTTP body */ +#define DC_LWS_MAX_RX DC_LWS_BUF /* max bytes a websocket may handle in a single receive */ +#define DC_LWS_MAX_FD 64 /* max file descriptors LWS will have open. if unset, LWS acquires all unused */ +#define DC_LWS_MAX_HEADER_LENGTH 64 /* _MAX_HEADER_LENGTH*_HEADERS_LENGTH is allocated for every reque */ +/* it's strongly recommended to calloc structs during initialization. */ +enum dc_status { /* theese are flags and should be and-checked */ + DC_UNSET = 0, /* default value when enum is calloced */ + DC_INCOMPLETE = 1 << 1, /* struct SHALL NOT be used by the ui, it is yet to be filled by api */ + DC_OK = 1 << 2, /* success status, api usually sets this after completion/filling of the strct */ + DC_BAD_LOGIN = 1 << 3, /* login failed */ + DC_VERIFICATION_NEEDED = 1 << 4, /* login: check email, click link/reg: tough luck ur IP flagd */ + DC_CAPTCHA_NEEDED = 1 << 5, /* must solve captcha, tough luck, not impl, use browser login */ + DC_BAD_USERNAME = 1 << 6, /* provided username can't be registered */ + DC_BAD_EMAIL = 1 << 7, /* provided email can't be registered */ + DC_NOT_FOUND = 1 << 8, /* when querying for roles/users/... received a 404 */ + DC_CONTINUE = 1 << 9, /* attached handlers return this to continue processing this output, N/I */ + DC_BREAK = 1 << 10, /* attached handlers return this to stop processing this output */ + DC_BODY_SENT = 1 << 11, /* LWS already sent body, it's now time to send _FINAL for H2 */ + DC_MUST_FREE = 1 << 12 /* cb pass: body must be freed when request is done with user_data */ +}; +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 +}; /* all enum fields here have values same as the values that the server sends */ +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. */ +}; +enum dc_api_io_type { + /* all output structs have important structs set when an output is broadcast, for example if we get a channel from server but we don't possess it's guild (by id), api will wait for the guild and hold the channel in it's cache. for messages, tags in message are also parsed and queried. roles in premissions in channels are also fetched but not users in permissions, because permissions are currently only used for checking our permissions, not others' */ + /* none of the outputs transfer memory ownership. tr0 for inputs means transfer none, trp means pointer to struct owned by library is to be passed, tr1 means transfer of ownership to library */ + /* network requests are sent to the event-based network library immediatley on input. network library is polled for incoming network traffic on each _event call. not totally filled structs in cache are checked and if one is/becomes filled, it will be output. if a role is requested when this role already exists and is searched for (not filled), for example because a channel needs the role to be output or the user created another search for this role before, this search will be dropped and the passed struct will be freed. */ + /* this library has internally attached functions for handling many io types so that the library nicely detects when for example a new role is received. the on-role-received function for example checks cache and updates structs that need this role */ + /* struct dc_program * serves as a handle for the API. create it with dc_program_init (may return NULL which indicates a LWS init failure) and destroy it with dc_program_free. pass it to every call inside struct dc_api_io.program */ + DC_API_NONE, /* i: N/A */ + /* o: nothing to output */ + DC_API_MESSAGE, /* i: send a message-tr0 or edit a message that was already sent-trp */ + /* o: message notification: GUI spawn or respawn if edited. */ + DC_API_CHANNEL, /* i: TODO: create a channel-tr0 */ + /* o: new channel created, GUI spawn it. */ + DC_API_GUILD, /* i: TODO: create a guild-tr0 */ + /* o: TODO: new guild created, GUI spawn it */ + DC_API_LOGIN, /* i: pass a dc_client-tr1, to relogin FIX prev retd cl not create new */ + /* o: the previously passed dc_client with set status */ + DC_API_LOGIN_CB,/* i: used internally for passing response from http client to api, see source */ + /* o: n/a */ + DC_API_REGISTER,/* i: pass a dc_client, to relogin FIX pr rt cl&cl->user not creat new */ + /* o: the previously passed dc_client with set status */ + DC_API_STATUS, /* i: N/A */ + /* o: new status message, pointer to internal string - tr0 */ + DC_API_USER, /* i: query for user by id, pass dc_user-tr1 */ + /* o: prev passed dc_user but filled (or not: ->status may indicate error) */ + DC_API_ROLE, /* i: query for role by id, pass dc_role-tr1 */ + /* o: prev passed dc_role but filled (or not: ->status may indicate error) */ + DC_API_ATTACH /* i: attaches function to handle output types */ + /* o: N/A */ +}; +/* enum dc_status (* dc_api_attached_func) (struct dc_api_io, void * data); */ /* i tried simplifying but didn't work */ +struct dc_api_io { /* output struct does NOT contain void * data (user pointer) from the input struct! */ + DC_STRUCT_PREFIX /* mostly useless here but it's only a couple of bytes so wth */ + enum dc_api_io_type type; /* NOTE: ALL POINTERS HERE ARE NOFREE */ + enum dc_status status; + void * body; + char * string; + struct dc_message * message; /* for safety and to mininize casting, each type has a pointer */ + struct dc_channel * channel; + struct dc_guild * guild; + struct dc_client * client; + struct dc_user * user; + struct dc_role * role; + struct dc_program * program; /* this is library handle */ + struct dc_attached_function * attached_function; /* points to allocated struct when DC_API_ATTAH */ + struct dc_lws_pass * pass; +}; +void dc_api_io_free (struct dc_api_io * s) { + if (!s) + return; + free(s); + return; +} +enum dc_lws_headers { + DC_LWS_AUTHORIZATION, + DC_LWS_HEADERS_LENGTH +} +char dc_lws_headers[][] = { + "Authorization:", +}; +struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */ + char * body; /* this contains post body and when _CB is called, it contains response */ + size_t body_length; /* body is NULL terminated - note0 in src/api.c */ + char headers[DC_LWS_HEADERS_LENGTH][DC_LWS_MAX_HEADER_LENGTH]; /* nofree, a static 2d array */ + int status; /* HTTP response code /\ headers contain request headers, then resp. */ + struct dc_api_io api_io; /* so dc_api_io can decide what shall be passed into _CB */ +}; +struct dc_client { + DC_STRUCT_PREFIX + 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 */ + enum dc_status status; +}; +struct dc_client * dc_client_init () { + struct dc_client * s = calloc(1, sizeof(*s)); + return s; +} +void dc_client_free (struct dc_client * s) { + if (!s) + return; + free(s->authorization); + free(s->email); + free(s->password); + free(s); +} +struct dc_guild { + DC_STRUCT_PREFIX + char * name; /* yesfree */ + char * alternative_messages_url; /* yesfree, internal - for virtual DMs guild */ + unsigned long long int id; /* 0 for virtual DMs guild */ + struct dc_client * client; /* nofree */ + 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; +#ifdef DC_UI_GTK + GtkTreeIter iter; /* NOTE: only works when GtkTreeModel has a flag GTK_TREE_MODEL_ITERS_PERSIST; see paragraph 8 of description of file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeModel.html */ + gboolean is_iter; + /* GtkTreeRowReference * row; */ /* yesfree - indicating the row */ /* not used: IRC message: "00:51:29 @Company | also: Don't create too many tree row references, those things are slow" */ +#endif +}; +struct dc_guild * dc_guild_init () { + struct dc_guild * s = calloc(1, sizeof(*s)); + return s; +} +void dc_guild_free (struct dc_guild * s) { + if (!s) + return; + free(s->name); + free(s->alternative_messages_url); + free(s); +} +struct dc_channel { + DC_STRUCT_PREFIX + 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_permission * permission; /* nofree - first permission */ +#ifdef DC_UI_GTK + GtkTreeIter iter; /* see notes of struct dc_guild */ + gboolean is_iter; /* XXX use this with caution: only if it's possible for treemodels to contains orphans, this works. otherwise, parents will be removed first and then channels will not het their is_iters invalidated. guild's is_iter will always get invalidated. file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeStore.html#gtk-tree-store-iter-is-valid could be a solution but it's said to be slow. */ +#endif +}; +struct dc_channel * dc_channel_init () { + struct dc_channel * s = calloc(1, sizeof(*s)); + return s; +} +void dc_channel_free (struct dc_channel * s) { + if (!s) + return; + free(s->name); + free(s->topic); + free(s); +} +struct dc_message { + DC_STRUCT_PREFIX + char * message; /* yesfree */ + char * attachment; /* yesfree - this is a HTTP URL. it would be nice to request file and store to ~/.cache/discord.c/ and only then use (RENDER IMAGE) from disk */ + struct dc_channel * channel; /* nofree */ + struct dc_user * user; /* nofree */ + time_t time; + unsigned long long int id; + struct dc_message * next; /* next message (linked list of all messages of dc_channel) */ + struct dc_message * reply; /* nofree - this message replies to another message or NULL */ +}; +struct dc_message * dc_message_init () { + struct dc_message * s = calloc(1, sizeof(*s)); + return s; +} +void dc_message_free (struct dc_message * s) { + if (!s) + return; + free(s->message); + free(s->attachment); + free(s); +} +struct dc_role { + DC_STRUCT_PREFIX + char * name; /* yesfree */ + unsigned long long int id; + 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 * role_membership; /* nofree - first role membership ll) */ + enum dc_status status; +}; +struct dc_role * dc_role_init () { + struct dc_role * s = calloc(1, sizeof(*s)); + return s; +} +void dc_role_free (struct dc_role * s) { + if (!s) + return; + free(s->name); + free(s); +} +struct dc_role_membership { + DC_STRUCT_PREFIX + struct dc_channel * channel; /* nofree */ + struct dc_user * user; /* nofree */ + struct dc_role * role; /* nofree */ + struct dc_role_membership * next; /* nofree - next role membership (lili in role) */ +}; +struct dc_role_membership * dc_role_membership_init () { + struct dc_role_membership * s = calloc(1, sizeof(*s)); + return s; +} +void dc_role_membership_free (struct dc_role_membership * s) { + if (!s) + return; + free(s); +} +struct dc_user { + DC_STRUCT_PREFIX + char * username; /* yesfree */ + unsigned long long int id; + short int discriminator; + enum dc_status status; +}; +struct dc_user * dc_user_init () { + struct dc_user * s = calloc(1, sizeof(*s)); + return s; +} +void dc_user_free (struct dc_user * s) { + if (!s) + return; + free(s->username); + free(s); +} +struct dc_permission { /* permissions can be individual on a per-channel basis */ + DC_STRUCT_PREFIX + 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; /* nofree - non-null if permission applies to a user */ + struct dc_role * role; /* nofree - non-null if it applies to a role */ + struct dc_permission * next; /* nexrt permission in ll in channel */ +}; /* permissions are only useful for checking OUR permissions, not others'. keep that in mind. */ +struct dc_permission * dc_permission_init () { + struct dc_permission * s = calloc(1, sizeof(*s)); + return s; +} +void dc_permission_free (struct dc_permission * s) { + if (!s) + return; + free(s); +} +struct dc_attached_function { + DC_STRUCT_PREFIX + enum dc_api_io_type type; + void * user_data; /* I hope I don't confuse this with void * data in DC_STRUCT_PREFIX */ + enum dc_status (* function) (struct dc_api_io, void *); +}; +struct dc_attached_function * dc_attached_function_init () { + struct dc_attached_function * s = calloc(1, sizeof(*s)); + return s; +} +void dc_attached_function_free (struct dc_attached_function * s) { + if (!s) + return; + free(s); +} +static int dc_lws_cb (struct lws *, enum lws_callback_reasons, void *, void *, size_t); +static const struct lws_protocols dc_lws_protocols[] = { + {"dc", dc_lws_cb, sizeof(struct dc_lws_pass), DC_LWS_MAX_RX, 0, NULL, 0}, + {NULL, NULL, 0, 0, 0, NULL, 0} +}; +#define DC_ISASQ(shortname) DC_ISA(struct dc_##shortname, shortname##s) /* in struct array of structs quick */ +struct dc_program { /* data storage and token used for communication with the library */ + DC_STRUCT_PREFIX /* this is the only struct that contains DC_ISAs */ + DC_ISASQ(client); /* yesfree */ + DC_ISASQ(guild); /* yesfree */ + DC_ISASQ(channel); /* yesfree */ + DC_ISASQ(message); /* yesfree */ + DC_ISASQ(role); /* yesfree */ + DC_ISASQ(role_membership); /* yesfree */ + DC_ISASQ(user); /* yesfree */ + DC_ISASQ(permission); /* yesfree */ + DC_ISASQ(attached_function); /* yesfree */ + DC_ISASQ(api_io); /* yesfree */ + struct lws_context * lws_context; /* yesfree */ +}; /* ui functions MUST check if !(->status & DC_INCOMPLETE) before using array members, incompletes are yet to be filled by the api */ +#define DC_ISAS_INIT(type/* w/o struct */, name) do { name##_sizeof = DC_ALLOC_CHUNK; /* structs ISA */ \ + name = calloc(name##_sizeof, sizeof(struct type *)); } while (0) /* prep arr, NO INIT membrs */ +#define DC_ISASIQ(shortname) DC_ISAS_INIT(dc_##shortname, s->shortname##s) /* ISAS init quick */ +struct dc_program * dc_program_init () { + struct dc_program * s = calloc(1, sizeof(struct dc_program)); + DC_ISASIQ(client); + DC_ISASIQ(guild); + DC_ISASIQ(channel); + DC_ISASIQ(message); + DC_ISASIQ(role); + DC_ISASIQ(role_membership); + DC_ISASIQ(user); + DC_ISASIQ(permission); + DC_ISASIQ(attached_function); + DC_ISASIQ(api_io); + /* lws init */ + struct lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we are not listening - we are a client */ + info.protocols = dc_lws_protocols; + info.fd_limit_per_thread = DC_LWS_MAX_FD; + if (!(s->lws_context = lws_create_context(&info))) { + lwsl_err("lws init failed\n"); + return NULL; + } + return s; +} +#define DC_ISAF(shortname) for (size_t i = 0; i < s->shortname##s_sizeof; i++) /* ISA free */ \ + dc_##shortname##_free(s->shortname##s[i]); \ + free(s->shortname##s); /* no problem if we free past _lenght, as uninited are NULL */ +void dc_program_free (struct dc_program * s) { + if (!s) + return; + DC_ISAF(client); + DC_ISAF(guild); + DC_ISAF(channel); + DC_ISAF(message); + DC_ISAF(role); + DC_ISAF(role_membership); + DC_ISAF(user); + DC_ISAF(permission); + DC_ISAF(attached_function); + DC_ISAF(api_io); + lws_context_destroy(s->lws_context); + free(s); +} diff --git a/src/lib.c b/src/lib.c new file mode 100644 index 0000000..8563873 --- /dev/null +++ b/src/lib.c @@ -0,0 +1,12 @@ +int smprintf(char ** str, const char * format, ...) { /* allocates automaticalls (: */ + va_list ap, aq; + va_start(ap, format); + va_copy(aq, ap); + int len = vsnprintf(NULL, 0, format, ap); + *str = malloc(len+1); + if (len != vsprintf(str, format, ap)) + fprintf(stderr, "[BUG] !!! len1 != len2\n"); + va_end(ap); + va_end(aq); + return len; +} @@ -1,8 +1,25 @@ #include <stdio.h> #include <stdlib.h> +#include <math.h> +#include <libwebsockets.h> +#include <lib.c> #include <ui.c> #include <api.c> int main (int argc, char * argv[]) { - dc_ui(argc, argv); + struct dc_program * p = dc_program_init(); + struct dc_client * client = dc_client_init(); + lws_set_log_level(0xFF /* all message types */, NULL /* do not change output location - cerr */); + client->email = strdup(argv[1]); + client->password = strdup(argv[2]); + struct dc_api_io i = { + .program = p, + .type = DC_API_LOGIN, + .client = client + }; + dc_api_i(i); + while (1) + i = dc_api_o(i); + /* dc_ui(argc, argv); */ + dc_program_free(p); return 0; } @@ -8,104 +8,20 @@ unsigned char dc_ui_def_u[] = { }; 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) +#define DC_UI_GTK /* indicating that the default ui will be used */ +#include <h.c> +G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free) struct dc_ui_data { GtkBuilder * b; GKeyFile * k; }; -enum dc_permissions { /* other permissions exist, but are not implemented/understood */ - DC_ALL_PERMISSIONS = 1 << 3, /* this is incredibly retarded, why is this SEPARATE?!? - admins */ - DC_CHANNEL_VIEW = 1 << 10, /* all enum fields here have values same as the server values */ - DC_MESSAGE_SEND = 1 << 11, - DC_MESSAGE_READ = 1 << 16, /* na tistem vegova serverju sem lahko pošiljal ne pa bral sporočil */ - DC_VOICE_LISTEN = 1 << 20, - DC_VOICE_SPEAK = 1 << 21 -}; -enum dc_channel_type { /* other types exist, but are not implemented/understood */ - DC_TEXT, /* all enum fields here have values same as the values that the server sends */ - DC_DM, - DC_VOICE, - DC_DM2 /* retarded. server sometimes sends this... converted to DC_DM at parsing server resp. */ -}; -struct dc_program { /* parent struct of dc_client, in case multi-login will ever be implemented (no) */ - struct dc_client * clients; /* yesfree */ /* dc_program contains the storage of all */ - size_t clients_sizeof; /* structs in program. freeing is done from */ - struct dc_guild * guilds; /* yesfree */ /* here and clasification (chans of a guild) */ - size_t guilds_sizeof; /* is done via the use of linked lists. */ - struct dc_channel * channels; /* yesfree */ /* before a network query, this storage may be */ - size_t channels_sizeof; /* used to check if we already have the info */ - struct dc_message * messages; /* yesfree */ /* already. for example to get dc_user from */ - size_t messages_sizeof; /* user id-user may be available on another */ - struct dc_role * roles; /* yesfree */ /* guild. sizeof=length so make sure heap */ - size_t roles_sizeof; /* *alloc()ations are fast. they are on linux */ -}; /* http://ž.ga/linuxfast */ -struct dc_user { - unsigned long long int id; - short int discriminator; - char * username; /* yesfree */ -}; -struct dc_role { - unsigned long long int id; - char * name; /* yesfree */ - enum dc_permissions permissions; - struct dc_guild * guild; /* nofree - owner of the role */ - struct dc_role * next; /* nofree - next role (linked list of all roles of dc_guild) */ -}; -struct dc_role_membership { - struct dc_guild * guild; /* nofree */ - struct dc_user * user; /* nofree */ - struct dc_role * role; /* nofree */ -}; -struct dc_client { - char * authorization; /* yesfree - authorization header value */ - char * email; /* yesfree */ - char * password; /* yesfree */ - struct dc_user * user; /* nofree - logged in user */ - struct dc_guild * guild; /* nofree - first guild */ -}; -struct dc_guild { - char * name; /* yesfree */ - unsigned long long int id; /* 0 for virtual DMs guild */ - struct dc_client * client; /* nofree */ - char * alternative_messages_url; /* yesfree, internal - alternative messages url - for virtual DMs guild */ - struct dc_guild * next; /* nofree - next guild (linked list of all guilds of dc_client) */ - struct dc_channel * channel; /* nofree - first channel */ - struct dc_role * role; /* nofree - first role. NOTE: first role is always role with role ID that is same as guild ID and it is the @everyone role */ - enum dc_permissions permissions; -}; -struct dc_permission { /* permissions can be individual on a per-channel basis */ - struct dc_permission * next; /* nofree - next permission (linked list of all perms of channel) */ - enum dc_permissions allow; - enum dc_permissions deny; - unsigned long long int id; /* to whom does this permission apply */ - struct dc_channel * channel; /* nofree - on which channel does it apply */ - struct dc_user user; /* non-null if permission applies to a user */ - struct dc_role role; /* non-null if it applies to a role */ - int type; /* 0=role, 1=member NOTE: user and role may not be filled at start, check id in case */ -}; /* permissions are only useful for checking OUR permissions, not others'. keep that in mind. */ -struct dc_channel { - char * name; /* yesfree - name */ - char * topic; /* yesfree - topic */ - unsigned long long int id; - enum dc_channel_type type; - struct dc_guild * guild; /* nofree */ - struct dc_channel * next; /* nofree - next channel (linked list of all guilds of dc_guild) */ - struct dc_message * message; /* nofree - first message (ordered by time) */ -}; -struct dc_message { - struct dc_channel * channel; /* nofree */ - struct dc_user * user; /* nofree */ - char * message; /* yesfree */ - char * attachment; /* yesfree */ - time_t time; - unsigned long long int id; - struct dc_message * next; /* next message (linked list of all messages of dc_channel) */ -}; /* # configuration file - loaded at startup, saved at exit, comments persist - description: [discord.c] multiline = true|false login = string password = string + strftime = format string */ void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m to clear messages */ size_t i = 0; @@ -113,10 +29,12 @@ void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m GtkWidget * w, * w2; #define DC_USMTL 32 char t[DC_USMTL]; + g_autoptr(gchar) c = g_key_file_get_string(d->k, "discord.c", "strftime", NULL); 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); + /* struct dc_channel * c = m->channel->guild->channel; */ /* XXX: I wrote this line of code but then I wen't to sleep and I can't remember what should I do here with the channel :shrug: */ return; } while ((w = gtk_grid_get_child_at(g, 0, i))) { /* now we get the index BEFORE which message will be placed */ @@ -128,23 +46,61 @@ void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m i++; /* BEFORE WHICH */ break; } + if (m->time <= before->time) /* we've found a spot at the top of the grid */ + break; if (m->time >= before->time && m->time <= after->time) { /* we've found a spot between two messages */ i++; /* SAME HERE. if there are no messages already, while will fail immediatley and i will remain 0 */ break; } + i++; } gtk_grid_insert_row(g, i); - gtk_grid_insert_column(g, i); + /* gtk_grid_insert_column(g, i); */ /* useless, we already *HAVE* columns */ b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0 /* spacing pixels */); - gtk_container_add(GTK_CONTAINER(b), gtk_label_new(m->user->username)); + snprintf(t, DC_USMTL, "%s#%04d", m->user->username, m->user->discriminator); + gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t)); /* 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 */ + strftime(t, DC_USMTL, c ? strcmp(c, "") ? c : "%c" : "%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); + g_object_set_data(G_OBJECT(b), "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 */ - } + /* 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); + gtk_widget_show_all(GTK_WIDGET(g)); + gtk_widget_show_all(GTK_WIDGET(g)); +} +void dc_ui_spawn_channel (struct dc_channel * c /* needs a functional guild or segfaults */, struct dc_ui_data * d) { /* adds a channel to the channel list, !c to clear all entries */ + GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree")); /* this func is transfer none */ + GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels")); + GtkTreeIter i; + if (!c) { + gtk_tree_store_clear(l); + struct dc_guild * g = c->guild->client->guild; + while (g) { + g->is_iter = FALSE; + struct dc_channel * c = g->channel; + while (c) { + c->is_iter = FALSE; + c = c->next; + } + g = g->next; + } + } + if (!c->guild->is_iter) { /* we don't have this guild already rendered */ + gtk_tree_store_insert(l, &i, NULL, -1 /* row position */); + gtk_tree_store_set(l, &i, 0, c->guild->name, -1); + gtk_tree_store_set(l, &i, 1, c->guild, -1); + memcpy(&c->guild->iter, &i, sizeof(GtkTreeIter)); + c->guild->is_iter = TRUE; + } + gtk_tree_store_insert(l, &i, &c->guild->iter, -1); /* for this to work, iter must not be same as parent! */ + gtk_tree_store_set(l, &i, 0, c->name, -1); /* TODO: set hover tooltip for c->topic */ + gtk_tree_store_set(l, &i, 1, c, -1); + memcpy(&c->iter, &i, sizeof(GtkTreeIter)); + c->is_iter = TRUE; + if (!gtk_tree_view_get_column(t, 0)) + gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL)); + gtk_tree_view_expand_all(t); } gchar * gtk_text_buffer_get_all_text(GtkTextBuffer * b) { GtkTextIter s, e; @@ -157,6 +113,7 @@ 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")))); + g_key_file_set_string(d->k, "discord.c", "strftime", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_strftime")))); 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) { @@ -186,6 +143,38 @@ void dc_ui_inputbox_activate (GtkWidget * a, struct dc_ui_data * d) { case 'C': dc_ui_spawn_message(NULL, d); break; + case 'd': /* debug create message TODO: delete before production useless */ + case 'D': /* /debug unixtimestamp message */ + ; + struct dc_user u; + u.username = "yeet"; + struct dc_message * m = malloc(sizeof(struct dc_message)); /* memory leak! */ + m->time = atoi((strchr(c, ' ') ? strchr(c, ' ') : c) + 1); + m->user = &u; + m->message = "test message"; + dc_ui_spawn_message(m, d); + break; + case 't': /* debug add string to the channel tree. TODO: delete before production useless */ + case 'T': /* /tree position string */ + ; + struct dc_channel * c = calloc(1, sizeof(struct dc_channel)); /* memory leak! */ + struct dc_guild * g = calloc(1, sizeof(struct dc_guild)); + c->guild = g; + c->name = "this is a parent."; + c->guild->name = "this is a child."; + dc_ui_spawn_channel(c, d); + break; + GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree")); + GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels")); + GtkTreeIter i; + GtkTreeIter j; + gtk_tree_store_insert(l, &i, NULL, 0); + gtk_tree_store_set(l, &i, 0, "eww", -1); + gtk_tree_store_insert(l, &j, &i, 0); /* for this to work, iter must not be the same as parent! */ + gtk_tree_store_set(l, &j, 0, "eww", -1); + if (!gtk_tree_view_get_column(t, 0)) + gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL)); + break; } else { /* send message to channel */ @@ -230,16 +219,37 @@ G_MODULE_EXPORT gboolean dc_ui_handle_close (GtkButton * b, gpointer u) { 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) { +void dc_ui_activate (GtkApplication * app, struct dc_ui_data * d) { 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); + 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 */ + 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); + /* gtk zdaj požene main loop */ +} +int dc_ui (int argc, char ** argv) { + GtkApplication *app; + int status; + struct dc_ui_data d; + gchar * s; + 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), &d); + /* pre app run setup */ + d.b = gtk_builder_new_from_string(dc_ui_def, -1); #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); @@ -249,31 +259,15 @@ void dc_ui_activate (GtkApplication * app, gpointer user_data) { 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(); + /* app run */ + status = g_application_run(G_APPLICATION(app), argc, argv); + gtk_main(); /* XXX: NO IDEA why this has to be run.... gtk_main loop should be started by g_application_run, but it doesn't do that FOR SOME REASON. HELP */ + /* post app cleanup */ 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); + /* dc_ui cleanup */ g_object_unref(app); return status; } diff --git a/src/ui.glade b/src/ui.glade index 555a70a..c2c727e 100644 --- a/src/ui.glade +++ b/src/ui.glade @@ -2,10 +2,298 @@ <!-- Generated with glade 3.22.1 --> <interface> <requires lib="gtk+" version="3.20"/> - <object class="GtkListStore" id="dc_main_tree"/> + <object class="GtkTreeStore" id="dc_main_tree"> + <columns> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + <!-- column-name gpointer1 --> + <column type="gpointer"/> + </columns> + </object> <object class="GtkTextBuffer" id="dc_message_input"> <signal name="changed" handler="dc_ui_inputbox_changed" swapped="no"/> </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" id="dc_main_channels"> + <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> + <property name="enable_tree_lines">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </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> + <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> <object class="GtkWindow" id="dc_window_registration"> <property name="can_focus">False</property> <property name="title" translatable="yes">Registration</property> @@ -97,7 +385,7 @@ <object class="GtkLabel"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">Username (>1 character)</property> + <property name="label" translatable="yes">Username (2-32 characters)</property> </object> <packing> <property name="left_attach">0</property> @@ -123,6 +411,9 @@ </packing> </child> <child> + <placeholder/> + </child> + <child> <object class="GtkLabel" id="dc_registration_response"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -133,13 +424,10 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> <child> - <placeholder/> - </child> - <child> <object class="GtkGrid"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -275,8 +563,7 @@ <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> + <property name="label" translatable="yes">Use a multiline textbox</property> </object> <packing> <property name="left_attach">0</property> @@ -305,6 +592,28 @@ <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">strftime(3) format</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="dc_settings_strftime"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="placeholder_text" translatable="yes">%c is the default</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> </object> <packing> <property name="expand">True</property> @@ -314,7 +623,7 @@ </child> <child> <object class="GtkButton" id="dc_settings_register"> - <property name="label" translatable="yes">Create an account</property> + <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> @@ -368,7 +677,7 @@ <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"/> + <signal name="activate" handler="dc_ui_handle_close" swapped="no"/> </object> <packing> <property name="left_attach">2</property> @@ -388,435 +697,4 @@ <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> |