summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authornirav <nirav@teisuu.com>2019-03-07 01:27:55 +0530
committerDandelion <nirav@teisuu.com>2019-03-07 01:27:55 +0530
commit6dd58a30761eca36544c4e815b36907eab084949 (patch)
treec1bc857a14fffe6f35f7405f133c0ed114aec1a4 /src
downloadap_client-6dd58a30761eca36544c4e815b36907eab084949.tar.gz
ap_client-6dd58a30761eca36544c4e815b36907eab084949.zip
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/account.h27
-rw-r--r--src/auth.c60
-rw-r--r--src/auth.h10
-rw-r--r--src/http.c162
-rw-r--r--src/http.h9
-rw-r--r--src/instance_info.c36
-rw-r--r--src/instance_info.h31
-rw-r--r--src/main.c121
-rw-r--r--src/option.c64
-rw-r--r--src/option.h14
-rw-r--r--src/status.c115
-rw-r--r--src/status.h31
-rw-r--r--src/string-util.c85
-rw-r--r--src/string-util.h9
-rw-r--r--src/timeline.c70
-rw-r--r--src/timeline.h14
-rw-r--r--src/window.c31
-rw-r--r--src/window.h7
18 files changed, 896 insertions, 0 deletions
diff --git a/src/account.h b/src/account.h
new file mode 100644
index 0000000..049c829
--- /dev/null
+++ b/src/account.h
@@ -0,0 +1,27 @@
+#ifndef __account_H
+#define __account_H
+
+#include <stdbool.h>
+
+struct account {
+ char *id;
+ char *username;
+ char *acct;
+ char *display_name;
+ bool locked;
+ unsigned int follower_count;
+ unsigned int following_count;
+ unsigned int statuses_count;
+ char *note;
+ char *url;
+ char *avatar;
+ char *avatar_static;
+ char *header;
+ char *header_static;
+ struct account *moved;
+ bool bot;
+};
+
+struct account *account_from_json(char *json_data);
+
+#endif
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..b0ba98c
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,60 @@
+#define _POSIX_C_SOURCE 200809L
+#include <err.h>
+#include <string.h>
+#include <jansson.h>
+#include "string-util.h"
+#include "auth.h"
+#include "http.h"
+
+#define CLIENT_NAME "ap_client"
+
+char *instance_domain;
+char *auth_token;
+
+static char *protocol = "https://";
+static char *app_register_url = "/api/v1/apps";
+
+int register_app(char *instance)
+{
+ json_t *root;
+ json_error_t error;
+ char *url;
+ char *req_data;
+ char *res_data;
+
+ root = json_pack_ex(&error, 1, "{s:s, s:s, s:s}", "client_name",
+ CLIENT_NAME, "redirect_uris", "urn:ietf:wg:oauth:2.0:oob", "scopes",
+ "read write push");
+
+ if (!root) {
+ fprintf(stderr, "register_app(): json pack error: line %d: %s\n",
+ error.line, error.text);
+ return -1;
+ }
+
+ req_data = json_dumps(root, 0);
+ json_decref(root);
+ if (!req_data) {
+ fprintf(stderr, "register_app(): failed to dump json\n");
+ return -1;
+ }
+
+ size_t s = strlen(protocol) + strlen(instance) + strlen(app_register_url) + 1;
+ url = malloc(s);
+ if (!url) {
+ err(1, "register_app(): ");
+ }
+
+ sprintf(url, "%s%s%s", protocol, instance, app_register_url);
+ res_data = post_request(url, req_data);
+ if (!res_data) {
+ free(req_data);
+ return -1;
+ }
+
+ printf("res: \n%s\n", res_data);
+
+ free(req_data);
+ free(res_data);
+ return 0;
+}
diff --git a/src/auth.h b/src/auth.h
new file mode 100644
index 0000000..d3e03ec
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,10 @@
+#ifndef __AUTH_H
+#define __AUTH_H
+
+extern char *instance_domain;
+extern char *auth_token;
+
+int register_app(char *instance);
+
+#endif
+
diff --git a/src/http.c b/src/http.c
new file mode 100644
index 0000000..35130af
--- /dev/null
+++ b/src/http.c
@@ -0,0 +1,162 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+#include "string-util.h"
+#include "auth.h"
+
+#define BUFFER_SIZE (256 * 1024)
+#define AUTH_HEADER_STR_PREFIX "Authorization: Bearer "
+
+int http_init()
+{
+ return curl_global_init(CURL_GLOBAL_ALL);
+}
+
+void http_cleanup()
+{
+ curl_global_cleanup();
+ return;
+}
+
+struct write_result {
+ char *data;
+ int pos;
+};
+
+static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ struct write_result *result = (struct write_result *)stream;
+
+ if (result->pos + size * nmemb >= BUFFER_SIZE - 1) {
+ fprintf(stderr, "error: too small buffer\n");
+ return 0;
+ }
+
+ memcpy(result->data + result->pos, ptr, size * nmemb);
+ result->pos += size * nmemb;
+
+ return size * nmemb;
+}
+
+char *get_request(const char *url)
+{
+ CURL *curl = NULL;
+ CURLcode status;
+ struct curl_slist *headers = NULL;
+ char *data = NULL;
+ long code;
+
+ curl = curl_easy_init();
+ if (!curl)
+ goto error;
+
+ data = malloc(BUFFER_SIZE);
+ if (!data)
+ goto error;
+
+ struct write_result write_result = {.data = data, .pos = 0};
+
+ if (auth_token) {
+ char *auth_header_val = malloc(strlen(AUTH_HEADER_STR_PREFIX) + strlen(auth_token) + 1);
+ strlcpy(auth_header_val, AUTH_HEADER_STR_PREFIX, sizeof(auth_header_val));
+ strlcat(auth_header_val, auth_token, sizeof(auth_header_val));
+ headers = curl_slist_append(headers, auth_header_val);
+ }
+
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result);
+
+ status = curl_easy_perform(curl);
+ if (status != 0) {
+ fprintf(stderr, "get_request(): unable to request data from %s: %s\n", url, curl_easy_strerror(status));
+ goto error;
+ }
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
+ if (code != 200) {
+ fprintf(stderr, "error: server responded with code %ld\n", code);
+ goto error;
+ }
+
+ data[write_result.pos] = '\0';
+
+ curl_slist_free_all(headers);
+ curl_easy_cleanup(curl);
+ return data;
+
+error:
+ if (data)
+ free(data);
+ if (headers)
+ curl_slist_free_all(headers);
+ if (curl)
+ curl_easy_cleanup(curl);
+ return NULL;
+}
+
+char *post_request(const char *url, char *post_data)
+{
+ CURL *curl = NULL;
+ CURLcode status;
+ struct curl_slist *headers = NULL;
+ char *data = NULL;
+ long code;
+
+
+ curl = curl_easy_init();
+ if (!curl)
+ goto error;
+
+ data = malloc(BUFFER_SIZE);
+ if (!data)
+ goto error;
+
+ struct write_result write_result = {.data = data, .pos = 0};
+
+ if (auth_token) {
+ char *auth_header_val = malloc(strlen(AUTH_HEADER_STR_PREFIX) + strlen(auth_token) + 1);
+ strlcpy(auth_header_val, AUTH_HEADER_STR_PREFIX, sizeof(auth_header_val));
+ strlcat(auth_header_val, auth_token, sizeof(auth_header_val));
+ headers = curl_slist_append(headers, auth_header_val);
+ }
+ if (post_data) {
+ char *content_type_header_val = "Content-Type: application/json";
+ headers = curl_slist_append(headers, content_type_header_val);
+ }
+
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
+
+ status = curl_easy_perform(curl);
+ if (status != 0) {
+ fprintf(stderr, "post_request(): unable to request data from %s: %s\n", url, curl_easy_strerror(status));
+ goto error;
+ }
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
+ if (code != 200) {
+ fprintf(stderr, "error: server responded with code %ld\n", code);
+ goto error;
+ }
+
+ data[write_result.pos] = '\0';
+
+ curl_slist_free_all(headers);
+ curl_easy_cleanup(curl);
+ return data;
+
+error:
+ if (data)
+ free(data);
+ if (headers)
+ curl_slist_free_all(headers);
+ if (curl)
+ curl_easy_cleanup(curl);
+ return NULL;
+}
diff --git a/src/http.h b/src/http.h
new file mode 100644
index 0000000..d7ba313
--- /dev/null
+++ b/src/http.h
@@ -0,0 +1,9 @@
+#ifndef __HTTP_H
+#define __HTTP_H
+
+int http_init();
+void http_cleanup();
+char *get_request(const char *url);
+char *post_request(const char *url, char *data);
+
+#endif
diff --git a/src/instance_info.c b/src/instance_info.c
new file mode 100644
index 0000000..6dab351
--- /dev/null
+++ b/src/instance_info.c
@@ -0,0 +1,36 @@
+#define _POSIX_C_SOURCE 200809L
+#include <string.h>
+#include <jansson.h>
+#include "instance_info.h"
+
+struct instance_info *instance_info_from_json(char *json_data)
+{
+ struct instance_info *info;
+ info = malloc(sizeof(struct instance_info));
+
+ json_t *root;
+ json_error_t error;
+
+ root = json_loads(json_data, 0, &error);
+
+ if (!root) {
+ fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
+ return NULL;
+ }
+
+ if (!json_is_object(root)) {
+ fprintf(stderr, "root is not object");
+ json_decref(root);
+ return NULL;
+ }
+
+ json_t *title = json_object_get(root, "title");
+ if (!json_is_string(title)) {
+ fprintf(stderr, "title is not string");
+ return NULL;
+ }
+ info->title = strdup(json_string_value(title));
+
+ json_decref(root);
+ return info;
+}
diff --git a/src/instance_info.h b/src/instance_info.h
new file mode 100644
index 0000000..72a5bbb
--- /dev/null
+++ b/src/instance_info.h
@@ -0,0 +1,31 @@
+#ifndef __INSTANCE_INFO_H
+#define __INSTANCE_INFO_H
+
+#include <stdbool.h>
+
+struct instance_info_urls {
+ char *streaming_api;
+};
+
+struct instance_info_stats {
+ int user_count;
+ int status_count;
+ int domain_count;
+};
+
+struct instance_info {
+ char *url;
+ char *title;
+ char *description;
+ char *email;
+ char *version;
+ struct instance_info_urls urls;
+ struct instance_info_stats states;
+ char *thumbnail;
+ char **languages;
+ bool registrations;
+};
+
+struct instance_info *instance_info_from_json(char *json_data);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..69778bd
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,121 @@
+#define _POSIX_C_SOURCE 200809L
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <gtk-3.0/gtk/gtk.h>
+#include "auth.h"
+#include "http.h"
+#include "timeline.h"
+#include "window.h"
+
+static GtkWidget *window;
+static GtkWidget *box;
+static GtkWidget *instance_name_box, *email_box, *password_box;
+static GtkWidget *submit_button;
+
+static void submit_login_form()
+{
+ const char *instance_name, *email, *password;
+ instance_name = gtk_entry_get_text(GTK_ENTRY(instance_name_box));
+ email = gtk_entry_get_text(GTK_ENTRY(email_box));
+ password = gtk_entry_get_text(GTK_ENTRY(password_box));
+ g_print("\n%s\n%s\n%s\n", instance_name, email, password);
+ char *in = strdup(instance_name);
+ int ok;
+ ok = register_app(in);
+ fprintf(stderr, "submit_login_form(): return val %d\n", ok);
+ free(in);
+}
+
+static void submit_button_clicked(GtkButton *button, gpointer user_data)
+{
+ submit_login_form();
+}
+
+static void activate(GtkApplication *app, gpointer user_data)
+{
+ window = gtk_application_window_new(app);
+ gtk_window_set_title(GTK_WINDOW(window), "ap_client");
+ gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+
+ box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_valign(GTK_WIDGET(box), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign(GTK_WIDGET(box), GTK_ALIGN_CENTER);
+
+ instance_name_box = gtk_entry_new();
+ gtk_entry_set_placeholder_text(
+ GTK_ENTRY(instance_name_box), "Instance domain");
+
+ email_box = gtk_entry_new();
+ gtk_entry_set_placeholder_text(GTK_ENTRY(email_box), "Email");
+
+ password_box = gtk_entry_new();
+ gtk_entry_set_placeholder_text(GTK_ENTRY(password_box), "Password");
+
+ submit_button = gtk_button_new();
+ g_signal_connect(GTK_BUTTON(submit_button), "clicked",
+ G_CALLBACK(submit_button_clicked), G_OBJECT(window));
+ gtk_button_set_label(GTK_BUTTON(submit_button), "Submit");
+
+ gtk_container_add(GTK_CONTAINER(window), box);
+ gtk_container_add(GTK_CONTAINER(box), instance_name_box);
+ gtk_container_add(GTK_CONTAINER(box), email_box);
+ gtk_container_add(GTK_CONTAINER(box), password_box);
+ gtk_container_add(GTK_CONTAINER(box), submit_button);
+
+ gtk_widget_show_all(window);
+}
+
+int main(int argc, char **argv)
+{
+ /* gtk_init(&argc, &argv); */
+ /* create_main_window(); */
+ /* gtk_main(); */
+ if (http_init()) {
+ fprintf(stderr, "main(): failed to load http library\n");
+ return EXIT_FAILURE;
+ }
+
+ GtkApplication *app;
+ int status;
+
+ app = gtk_application_new("org.gtk.ap_client", G_APPLICATION_FLAGS_NONE);
+ g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
+ status = g_application_run(G_APPLICATION(app), argc, argv);
+ g_object_unref(app);
+
+ http_cleanup();
+
+ return status;
+
+ /* char *text; */
+ /* struct timeline *t; */
+ /* char *url = "https://mstdn.io/api/v1/timelines/home"; */
+ /* char *token = */
+ /* "580acfb412e927c2030fb5519f417b03ced1482c862d44376922cd81ee8a3655";
+ */
+
+ /* text = request(url, token); */
+ /* if (!text) { */
+ /* fprintf(stderr, "main(): failed to get http response\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ /* t = timeline_from_json(text); */
+ /* if (t == NULL) { */
+ /* fprintf(stderr, "main(): failed to parse timeline\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ /* for (size_t i = 0; i < t->size; i++) { */
+ /* printf("status id: %s\n", t->statuses[i]->id); */
+ /* printf("content: %s\n", t->statuses[i]->content); */
+ /* printf("reblog count: %d\n", t->statuses[i]->reblogs_count); */
+ /* printf("fav count: %d\n", t->statuses[i]->favourites_count); */
+ /* printf("\n"); */
+ /* } */
+
+ /* free(text); */
+ /* timeline_free(t); */
+}
diff --git a/src/option.c b/src/option.c
new file mode 100644
index 0000000..011bd94
--- /dev/null
+++ b/src/option.c
@@ -0,0 +1,64 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include "option.h"
+
+static const char version[] = "qwe version 0.01";
+static const char usage[] =
+ "Usage: qwe [options...] <file>\n"
+ "\n"
+ " -i Hide info bar by default.\n"
+ " -f Use fullscreen mode by default.\n"
+ " -h Show help message and quit.\n"
+ " -v Show the version number and quit.\n";
+
+void print_usage()
+{
+ printf("%s\n", usage);
+}
+
+void print_version()
+{
+ printf("%s\n", version);
+}
+
+struct option _options;
+const struct option *options = (const struct option *)&_options;
+
+void parse_options(int argc, char **argv)
+{
+ // default options
+ _options.fullscreen = false;
+ _options.show_info = true;
+
+ // override options from commandline parameters
+ int opt;
+ while ((opt = getopt(argc, argv, "hvif")) != -1) {
+ switch (opt) {
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 'h':
+ print_usage();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case 'i':
+ _options.show_info = false;
+ break;
+ case 'f':
+ _options.fullscreen = true;
+ break;
+ }
+ }
+
+ if (optind >= argc) {
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ _options.file_name = argv[optind];
+}
diff --git a/src/option.h b/src/option.h
new file mode 100644
index 0000000..33b8335
--- /dev/null
+++ b/src/option.h
@@ -0,0 +1,14 @@
+#ifndef __OPTION_H
+#define __OPTION_H
+
+#include <stdbool.h>
+
+struct option {
+ char *file_name;
+ bool fullscreen;
+ bool show_info;
+};
+extern const struct option *options;
+void parse_options(int argc, char **argv);
+
+#endif
diff --git a/src/status.c b/src/status.c
new file mode 100644
index 0000000..f96267f
--- /dev/null
+++ b/src/status.c
@@ -0,0 +1,115 @@
+#define _POSIX_C_SOURCE 200809L
+#include <err.h>
+#include <string.h>
+#include <jansson.h>
+#include "status.h"
+
+struct status *status_from_json(char *json_data)
+{
+ json_t *root;
+ json_error_t error;
+
+ root = json_loads(json_data, 0, &error);
+ if (!root) {
+ fprintf(stderr, "status_from_json(): json parse error: line %d: %s\n",
+ error.line, error.text);
+ return NULL;
+ }
+
+ return status_from_json_t(root);
+}
+
+struct status *status_from_json_t(json_t *root)
+{
+ struct status *s;
+ if (!root) {
+ fprintf(stderr, "status_from_json_t(): json data is null\n");
+ return NULL;
+ }
+
+ if (!json_is_object(root)) {
+ fprintf(stderr, "status_from_json_t(): json root is not object\n");
+ json_decref(root);
+ return NULL;
+ }
+
+ s = calloc(1, sizeof(struct status));
+ if (!s) {
+ err(1, "status_from_json_t(): failed to allocate memory");
+ json_decref(root);
+ return NULL;
+ }
+
+ json_t *id = json_object_get(root, "id");
+ if (json_is_string(id)) {
+ s->id = strdup(json_string_value(id));
+ }
+
+ json_t *uri = json_object_get(root, "uri");
+ if (json_is_string(uri)) {
+ s->uri = strdup(json_string_value(uri));
+ }
+
+ json_t *url = json_object_get(root, "url");
+ if (json_is_string(url)) {
+ s->url = strdup(json_string_value(url));
+ }
+
+ json_t *in_reply_to_id = json_object_get(root, "in_reply_to_id");
+ if (json_is_string(in_reply_to_id)) {
+ s->in_reply_to_id = strdup(json_string_value(in_reply_to_id));
+ }
+
+ json_t *in_reply_to_account_id =
+ json_object_get(root, "in_reply_to_account_id");
+ if (json_is_string(in_reply_to_account_id)) {
+ s->in_reply_to_account_id =
+ strdup(json_string_value(in_reply_to_account_id));
+ }
+
+ json_t *content = json_object_get(root, "content");
+ if (json_is_string(content)) {
+ s->content = strdup(json_string_value(content));
+ }
+
+ json_t *replies_count = json_object_get(root, "replies_count");
+ if (json_is_integer(replies_count)) {
+ s->replies_count = json_integer_value(replies_count);
+ }
+
+ json_t *reblogs_count = json_object_get(root, "reblogs_count");
+ if (json_is_integer(reblogs_count)) {
+ s->reblogs_count = json_integer_value(reblogs_count);
+ }
+
+ json_t *favourites_count = json_object_get(root, "favourites_count");
+ if (json_is_integer(favourites_count)) {
+ s->favourites_count = json_integer_value(favourites_count);
+ }
+
+ json_decref(root);
+ return s;
+}
+
+void status_free(struct status *s)
+{
+ if (!s) {
+ fprintf(stderr, "status_free(): status not initializes");
+ return;
+ }
+ if (s->id)
+ free(s->id);
+ if (s->uri)
+ free(s->uri);
+ if (s->url)
+ free(s->url);
+ if (s->in_reply_to_id != NULL)
+ free(s->in_reply_to_id);
+ if (s->in_reply_to_account_id)
+ free(s->in_reply_to_account_id);
+ if (s->reblog)
+ status_free(s->reblog);
+ if (s->content)
+ free(s->content);
+ free(s);
+}
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 0000000..ede3540
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,31 @@
+#ifndef __STATUS_H
+#define __STATUS_H
+
+#include <stdbool.h>
+#include <jansson.h>
+
+#include "account.h"
+
+struct status {
+ char *id;
+ char *uri;
+ char *url;
+ struct account *account;
+ char *in_reply_to_id;
+ char *in_reply_to_account_id;
+ struct status *reblog;
+ char *content;
+ unsigned int replies_count;
+ unsigned int reblogs_count;
+ unsigned int favourites_count;
+ bool reblogged;
+ bool favourited;
+ bool muted;
+ bool sensitive;
+};
+
+struct status *status_from_json(char *);
+struct status *status_from_json_t(json_t *);
+void status_free(struct status *);
+
+#endif
diff --git a/src/string-util.c b/src/string-util.c
new file mode 100644
index 0000000..e183288
--- /dev/null
+++ b/src/string-util.c
@@ -0,0 +1,85 @@
+/* $OpenBSD: src/lib/libc/string/strlcpy.c,v 1.11 2006/05/05 15:27:38
+ * millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return (dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return (dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/src/string-util.h b/src/string-util.h
new file mode 100644
index 0000000..5bf6f11
--- /dev/null
+++ b/src/string-util.h
@@ -0,0 +1,9 @@
+#ifndef __STRING_UTIL_H
+#define __STRING_UTIL_H
+
+#include <sys/types.h>
+
+size_t strlcpy(char *dst, const char *src, size_t siz);
+size_t strlcat(char *dst, const char *src, size_t siz);
+
+#endif
diff --git a/src/timeline.c b/src/timeline.c
new file mode 100644
index 0000000..c2ca486
--- /dev/null
+++ b/src/timeline.c
@@ -0,0 +1,70 @@
+#define _POSIX_C_SOURCE 200809L
+#include <err.h>
+#include <string.h>
+#include <jansson.h>
+#include "timeline.h"
+
+struct timeline *timeline_from_json(char *json_data)
+{
+ json_t *root;
+ json_error_t error;
+ struct timeline *t;
+
+ root = json_loads(json_data, 0, &error);
+
+ if (!root) {
+ fprintf(stderr, "timeline_free(): json root it null\n");
+ return NULL;
+ }
+
+ if (!json_is_array(root)) {
+ fprintf(stderr, "timeline_free(): timeline not initialized\n");
+ json_decref(root);
+ return NULL;
+ }
+
+ t = calloc(1, sizeof(struct timeline));
+ if (!t) {
+ err(1, "timeline_from_json(): failed to allocate memory");
+ json_decref(root);
+ return NULL;
+ }
+
+ t->size = json_array_size(root);
+ t->statuses = calloc(t->size, sizeof(struct status *));
+ if (!(t->statuses)) {
+ err(1, "timeline_from_json(): failed to allocate memory");
+ json_decref(root);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < t->size; i++) {
+ json_t *data;
+ data = json_array_get(root, i);
+ if (!data)
+ goto error;
+ t->statuses[i] = status_from_json_t(data);
+ if (!(t->statuses[i]))
+ goto error;
+ }
+ json_decref(root);
+ return t;
+
+error:
+ timeline_free(t);
+ json_decref(root);
+ return NULL;
+}
+
+void timeline_free(struct timeline *t)
+{
+ if (!t) {
+ fprintf(stderr, "timeline_free(): timeline not initialized\n");
+ return;
+ }
+ for (size_t i = 0; i < t->size; i++) {
+ if (t->statuses[i])
+ status_free(t->statuses[i]);
+ }
+ free(t);
+}
diff --git a/src/timeline.h b/src/timeline.h
new file mode 100644
index 0000000..f21b3b6
--- /dev/null
+++ b/src/timeline.h
@@ -0,0 +1,14 @@
+#ifndef __TIMELINE_H
+#define __TIMELINE_H
+
+#include "status.h"
+
+struct timeline {
+ struct status **statuses;
+ size_t size;
+};
+
+struct timeline *timeline_from_json(char *);
+void timeline_free(struct timeline *);
+
+#endif
diff --git a/src/window.c b/src/window.c
new file mode 100644
index 0000000..02c6073
--- /dev/null
+++ b/src/window.c
@@ -0,0 +1,31 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdbool.h>
+#include <gtk-3.0/gtk/gtk.h>
+
+#define WINDOW_TITLE "ap_client"
+
+GtkWidget *window;
+GtkWidget *scrolled_window;
+
+void create_main_window()
+{
+ // root window
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+ gtk_widget_set_name(GTK_WIDGET(window), "main-window");
+ gtk_window_set_title(GTK_WINDOW(window), WINDOW_TITLE);
+ gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
+
+ // scrolled window
+ scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_set_vexpand(GTK_WIDGET(scrolled_window), true);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ GtkWidget *viewport = gtk_viewport_new(NULL, NULL);
+ gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(viewport));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(scrolled_window));
+ gtk_widget_show_all(GTK_WIDGET(window));
+}
+
+
+
diff --git a/src/window.h b/src/window.h
new file mode 100644
index 0000000..947cdbe
--- /dev/null
+++ b/src/window.h
@@ -0,0 +1,7 @@
+#ifndef __WINDOW_H
+#define __WINDOW_H
+
+void create_main_window();
+
+#endif
+