diff --git a/CMakeLists.txt b/CMakeLists.txt index 628e905..3068991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + FIND_PACKAGE(PkgConfig) FIND_PACKAGE(Threads) diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake new file mode 100644 index 0000000..6708ba4 --- /dev/null +++ b/cmake/FindReadline.cmake @@ -0,0 +1,49 @@ +# Code copied from sethhall@github +# +# - Try to find readline include dirs and libraries +# +# Usage of this module as follows: +# +# find_package(Readline) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Readline_ROOT_DIR Set this variable to the root installation of +# readline if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# READLINE_FOUND System has readline, include and lib dirs found +# Readline_INCLUDE_DIR The readline include directories. +# Readline_LIBRARY The readline library. + +find_path(Readline_ROOT_DIR + NAMES include/readline/readline.h +) + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + HINTS ${Readline_ROOT_DIR}/include +) + +find_library(Readline_LIBRARY + NAMES readline + HINTS ${Readline_ROOT_DIR}/lib +) + +if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + set(READLINE_FOUND TRUE) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + FIND_LIBRARY(Readline_LIBRARY NAMES readline) + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) + MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + +mark_as_advanced( + Readline_ROOT_DIR + Readline_INCLUDE_DIR + Readline_LIBRARY +) diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index 6419cae..d1d0ffd 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -3,11 +3,13 @@ #include #include +#include #include #include #include +#include struct dc_api_; typedef struct dc_api_ *dc_api_t; @@ -48,7 +50,11 @@ bool dc_api_authenticate(dc_api_t api, dc_account_t account); bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, dc_account_t user); +/** + * Fetch a list of guilds fro the specified login user. Warning if you + * unref the pointer array, you will also unref all the dc_guild_t objects. + */ bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, - GPtrArray *guilds); + GPtrArray **guilds); #endif diff --git a/libdc/src/account.c b/libdc/src/account.c index 0aa9ece..67be29c 100644 --- a/libdc/src/account.c +++ b/libdc/src/account.c @@ -126,8 +126,11 @@ bool dc_account_has_token(dc_account_t a) void dc_account_set_id(dc_account_t a, char const *id) { return_if_true(a == NULL,); - free(a->id); - a->id = strdup(id); + + if (a->id == NULL || strcmp(a->id, "@me")) { + free(a->id); + a->id = strdup(id); + } } char const *dc_account_id(dc_account_t a) diff --git a/libdc/src/api.c b/libdc/src/api.c index f0ae4f7..199b7d4 100644 --- a/libdc/src/api.c +++ b/libdc/src/api.c @@ -354,32 +354,58 @@ bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, cleanup: - if (reply != NULL) { - json_decref(reply); - reply = NULL; - } + free(url); + json_decref(reply); return ret; } -bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, - +bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) { char *url = NULL; - json_t *reply = NULL, *val = NULL; + json_t *reply = NULL, *c = NULL, *val = NULL; + size_t i = 0; bool ret = false; + GPtrArray *guilds = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); return_if_true(api == NULL, false); return_if_true(login == NULL, false); - return_if_true(user == NULL, false); - asprintf(&url, "users/%s/guilds", dc_account_id(user)); + asprintf(&url, "users/%s/guilds", dc_account_id(login)); reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); goto_if_true(reply == NULL, cleanup); + goto_if_true(!json_is_array(reply), cleanup); + + json_array_foreach(reply, i, c) { + dc_guild_t g = dc_guild_new(); + + val = json_object_get(c, "id"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_guild_set_id(g, json_string_value(val)); + + val = json_object_get(c, "name"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_guild_set_name(g, json_string_value(val)); + + g_ptr_array_add(guilds, g); + } + + *out = guilds; + guilds = NULL; + + ret = true; + cleanup: + free(url); json_decref(reply); + + if (guilds) { + g_ptr_array_unref(guilds); + guilds = NULL; + } + return ret; } diff --git a/libdc/src/guild.c b/libdc/src/guild.c index 16ca0a7..966f1bd 100644 --- a/libdc/src/guild.c +++ b/libdc/src/guild.c @@ -26,7 +26,7 @@ dc_guild_t dc_guild_new(void) p->ref.cleanup = (dc_cleanup_t)dc_guild_free; - return p; + return dc_ref(p); } char const *dc_guild_name(dc_guild_t d) diff --git a/libdc/src/loop.c b/libdc/src/loop.c index cef5c6b..f25aa99 100644 --- a/libdc/src/loop.c +++ b/libdc/src/loop.c @@ -199,7 +199,7 @@ bool dc_loop_once(dc_loop_t l) struct CURLMsg *msg = NULL; size_t i = 0; - ret = event_base_loop(l->base, EVLOOP_ONCE|EVLOOP_NONBLOCK); + ret = event_base_loop(l->base, EVLOOP_ONCE); if (ret < 0) { return false; } diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index 5b5f865..fd42499 100644 --- a/ncdc/CMakeLists.txt +++ b/ncdc/CMakeLists.txt @@ -1,8 +1,15 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0) +PKG_CHECK_MODULES(NCURSES REQUIRED ncurses) +PKG_CHECK_MODULES(PANEL REQUIRED panel) +FIND_PACKAGE(Readline) + SET(TARGET "ncdc") SET(SOURCES + "include/ncdc/mainwindow.h" + "include/ncdc/ncdc.h" + "src/mainwindow.c" "src/ncdc.c" ) @@ -13,10 +20,16 @@ INCLUDE_DIRECTORIES( ${CURL_INCLUDE_DIRS} ${EVENT_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS} + ${NCURSES_INCLUDE_DIRS} + ${PANEL_INCLUDE_DIRS} + ${Readline_INCLUDE_DIR} ) ADD_EXECUTABLE(${TARGET} ${SOURCES}) TARGET_LINK_LIBRARIES(${TARGET} ${DC_LIBRARIES} ${GLIB2_LIBRARIES} + ${NCURSES_LIBRARIES} + ${PANEL_LIBRARIES} + ${Readline_LIBRARY} ) diff --git a/ncdc/include/ncdc/mainwindow.h b/ncdc/include/ncdc/mainwindow.h new file mode 100644 index 0000000..16a44af --- /dev/null +++ b/ncdc/include/ncdc/mainwindow.h @@ -0,0 +1,14 @@ +#ifndef NCDC_MAINWINDOW_H +#define NCDC_MAINWINDOW_H + +#include + +struct ncdc_mainwindow_; +typedef struct ncdc_mainwindow_ *ncdc_mainwindow_t; + +bool ncdc_mainwindow_init(void); + +void ncdc_mainwindow_feed(int ch); +void ncdc_mainwindow_refresh(void); + +#endif diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index 4c00cfe..9395f2d 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -21,7 +21,14 @@ #include #include -//#define DEBUG +#include +#include +#include +#include + +#include +#include +#include #define return_if_true(v,r) do { if (v) return r; } while(0) #define goto_if_true(v,l) do { if (v) goto l; } while(0) diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c new file mode 100644 index 0000000..b923cb0 --- /dev/null +++ b/ncdc/src/mainwindow.c @@ -0,0 +1,213 @@ +#include +#include + +typedef enum { + FOCUS_GUILDS = 0, + FOCUS_CHAT, + FOCUS_INPUT, +} focus_t; + +struct ncdc_mainwindow_ +{ + dc_refable_t ref; + + WINDOW *guilds; + int guilds_w; + int guilds_h; + int guilds_y; + int guilds_x; + + WINDOW *chat; + int chat_h; + int chat_w; + int chat_y; + int chat_x; + + WINDOW *input; + int input_w; + int input_h; + int input_y; + int input_x; + + char *cmd; + int cin; + bool cin_ready; + + int focus; +}; + +static ncdc_mainwindow_t mainwin = NULL; + +static void mainwin_resize(void); +static void mainwin_update_focus(void); +static void mainwin_command(char *s); + +static int readline_input_avail(void) +{ + return mainwin->cin_ready; +} + +static int readline_getc(FILE *dummy) +{ + mainwin->cin_ready = false; + return mainwin->cin; +} + +static int measure(char const *string) +{ + size_t needed = mbstowcs(NULL, string, 0) + 1; + wchar_t *wcstring = calloc(needed, sizeof(wchar_t)); + size_t ret = 0; + + return_if_true(wcstring == NULL, -1); + + ret = mbstowcs(wcstring, string, needed); + if (ret == (size_t)-1) { + free(wcstring); + return -1; + } + + int width = wcswidth(wcstring, needed); + free(wcstring); + + return width; +} + +static void readline_redisplay(void) +{ + int len = 0; + char *line = NULL; + int diff = 0; + + asprintf(&line, "%s%s", + (rl_display_prompt != NULL ? rl_display_prompt : ""), + (rl_line_buffer != NULL ? rl_line_buffer : "") + ); + len = measure(line); + + diff = len - mainwin->input_w + 3; + if (diff > 0) { + memmove(line, line + diff, len - diff); + line[len-diff] = '\0'; + } + + werase(mainwin->input); + mvwprintw(mainwin->input, 1, 1, "%s", line); + free(line); + + wrefresh(mainwin->input); +} + +bool ncdc_mainwindow_init(void) +{ + mainwin = calloc(1, sizeof(struct ncdc_mainwindow_)); + return_if_true(mainwin == NULL, false); + + mainwin->guilds = newwin(5, 5, 1, 1); + mainwin->chat = newwin(5, 5, 4, 4); + mainwin->input = newwin(5, 5, 8, 8); + mainwin_resize(); + + mainwin->focus = FOCUS_INPUT; + mainwin_update_focus(); + + rl_getc_function = readline_getc; + rl_input_available_hook = readline_input_avail; + rl_redisplay_function = readline_redisplay; + rl_callback_handler_install("", mainwin_command); + + rl_catch_signals = 0; + rl_catch_sigwinch = 0; + rl_deprep_term_function = NULL; + rl_prep_term_function = NULL; + + return true; +} + +static void mainwin_resize(void) +{ + mainwin->guilds_h = LINES; + mainwin->guilds_w = (COLS / 5); + mainwin->guilds_y = 0; + mainwin->guilds_x = 0; + + wresize(mainwin->guilds, mainwin->guilds_h, mainwin->guilds_w); + mvwin(mainwin->guilds, mainwin->guilds_y, mainwin->guilds_x); + wnoutrefresh(mainwin->guilds); + + mainwin->input_h = 3; + mainwin->input_w = COLS - mainwin->guilds_w; + mainwin->input_y = LINES - mainwin->input_h; + mainwin->input_x = mainwin->guilds_w; + + wresize(mainwin->input, mainwin->input_h, mainwin->input_w); + mvwin(mainwin->input, mainwin->input_y, mainwin->input_x); + wnoutrefresh(mainwin->input); + + mainwin->chat_h = LINES - mainwin->input_h; + mainwin->chat_w = COLS - mainwin->guilds_w; + mainwin->chat_y = 0; + mainwin->chat_x = mainwin->guilds_w; + + wresize(mainwin->chat, mainwin->chat_h, mainwin->chat_w); + mvwin(mainwin->chat, mainwin->chat_y, mainwin->chat_x); + wnoutrefresh(mainwin->chat); +} + +static void mainwin_command(char *s) +{ + free(mainwin->cmd); + mainwin->cmd = s; +} + +static void mainwin_update_focus(void) +{ + switch (mainwin->focus) { + case FOCUS_GUILDS: + { + } break; + + case FOCUS_CHAT: + { + } break; + + case FOCUS_INPUT: + { + int x = 1, y = 1; + wmove(mainwin->input, y, x); + } break; + + } +} + +void ncdc_mainwindow_feed(int ch) +{ + switch (ch) { + case KEY_RESIZE: mainwin_resize(); break; + } + + switch (mainwin->focus) { + case FOCUS_INPUT: + { + mainwin->cin = ch; + mainwin->cin_ready = true; + rl_callback_read_char(); + } break; + + } +} + +void ncdc_mainwindow_refresh(void) +{ + /* move windows + */ + box(mainwin->guilds, 0, 0); + box(mainwin->chat, 0, 0); + box(mainwin->input, 0, 0); + + wrefresh(mainwin->guilds); + wrefresh(mainwin->chat); + wrefresh(mainwin->input); + + doupdate(); +} diff --git a/ncdc/src/ncdc.c b/ncdc/src/ncdc.c index 256ffb0..5e03984 100644 --- a/ncdc/src/ncdc.c +++ b/ncdc/src/ncdc.c @@ -1,8 +1,5 @@ #include - -#include -#include -#include +#include #include #include @@ -11,10 +8,13 @@ */ struct event *stdin_ev = NULL; +/* main window + */ +ncdc_mainwindow_t mainwin = NULL; + /* we loop in a different thread */ static bool done = false; -static pthread_t looper; char *dc_private_dir = NULL; char *dc_config_file = NULL; @@ -26,11 +26,14 @@ dc_api_t api = NULL; static void sighandler(int sig) { + endwin(); exit(3); } static void cleanup(void) { + endwin(); + if (stdin_ev != NULL) { event_del(stdin_ev); event_free(stdin_ev); @@ -38,7 +41,6 @@ static void cleanup(void) } done = true; - pthread_join(looper, NULL); dc_unref(api); dc_unref(loop); @@ -46,6 +48,12 @@ static void cleanup(void) static void stdin_handler(int sock, short what, void *data) { + int ch = 0; + + if ((what & EV_READ) == EV_READ) { + ch = getch(); + ncdc_mainwindow_feed(ch); + } } static bool init_everything(void) @@ -79,20 +87,7 @@ static bool init_everything(void) return true; } -static void *loop_thread(void *arg) -{ - while (!done) { - if (!dc_loop_once(loop)) { - break; - } - - usleep(10 * 1000); - } - - return NULL; -} - -static dc_account_t account_from_config(void) +dc_account_t account_from_config(void) { char const *email = NULL; char const *password = NULL; @@ -138,25 +133,32 @@ int main(int ac, char **av) } done = false; - if (pthread_create(&looper, NULL, loop_thread, &done)) { + + initscr(); + cbreak(); + noecho(); + nonl(); + intrflush(NULL, FALSE); + + if (has_colors()) { + start_color(); + use_default_colors(); + } + + if (!ncdc_mainwindow_init()) { + fprintf(stderr, "failed to init ncurses\n"); return 3; } - dc_account_t a = account_from_config(); - if (a == NULL) { - fprintf(stderr, "no account specified in config file; sho-sho!\n"); - return 3; + while (!done) { + ncdc_mainwindow_refresh(); + + if (!dc_loop_once(loop)) { + break; + } } - if (!dc_api_authenticate(api, a)) { - fprintf(stderr, "authentication failed, wrong password?\n"); - return 3; - } - - if (!dc_api_userinfo(api, a, a)) { - fprintf(stderr, "failed to get user information\n"); - return 3; - } + endwin(); return 0; }