messenger-cli

Command-line user interface for GNUnet Messenger
Log | Files | Refs | README | LICENSE

chat.c (8623B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2022--2024 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author Tobias Frisch
     22  * @file chat.c
     23  */
     24 
     25 #include "chat.h"
     26 
     27 #include "application.h"
     28 #include "util.h"
     29 #include <curses.h>
     30 #include <gnunet/gnunet_chat_lib.h>
     31 
     32 #ifndef MESSENGER_CLI_BINARY
     33 #define MESSENGER_CLI_BINARY "messenger_cli"
     34 #endif
     35 
     36 #ifndef MESSENGER_CLI_VERSION
     37 #define MESSENGER_CLI_VERSION "unknown"
     38 #endif
     39 
     40 static void
     41 _chat_refresh(MESSENGER_Application *app)
     42 {
     43   application_clear(app);
     44   chat_update_layout(&(app->chat), app);
     45 
     46   accounts_print(&(app->accounts), app);
     47   chats_print(&(app->chats), app);
     48   members_print(&(app->current.members));
     49   messages_print(&(app->current.messages));
     50 
     51   if (!app->ui.logo)
     52     return;
     53 
     54   werase(app->ui.logo);
     55   wmove(app->ui.logo, 0, 0);
     56 
     57   util_print_logo(app->ui.logo);
     58 
     59   int x = getcurx(app->ui.logo);
     60   int y = getcury(app->ui.logo);
     61 
     62   util_print_info(app->ui.logo, MESSENGER_CLI_VERSION);
     63 
     64   wmove(app->ui.logo, --y, x);
     65   util_print_info(app->ui.logo, MESSENGER_CLI_BINARY);
     66 }
     67 
     68 static bool
     69 _chat_event(MESSENGER_Application *app,
     70             int key)
     71 {
     72   if (key < 0)
     73     goto refresh;
     74 
     75   const struct GNUNET_CHAT_Account *account = GNUNET_CHAT_get_connected(
     76       app->chat.handle
     77   );
     78 
     79   if (!account)
     80     accounts_event(&(app->accounts), app, key);
     81   else if (app->chat.context)
     82   {
     83     if (app->chat.show_members)
     84       members_event(&(app->current.members), app, key);
     85     else
     86       messages_event(&(app->current.messages), app, key);
     87   }
     88   else
     89     chats_event(&(app->chats), app, key);
     90 
     91   if (app->chat.quit)
     92     return TRUE;
     93 
     94 refresh:
     95   _chat_refresh(app);
     96   return FALSE;
     97 }
     98 
     99 static int
    100 _chat_message(void *cls,
    101               struct GNUNET_CHAT_Context *context,
    102               struct GNUNET_CHAT_Message *message)
    103 {
    104   MESSENGER_Application *app = cls;
    105 
    106   chat_process_message(&(app->chat), context, message);
    107 
    108   _chat_event(app, KEY_RESIZE);
    109   return GNUNET_YES;
    110 }
    111 
    112 static void
    113 _chat_idle(void *cls)
    114 {
    115   MESSENGER_Application *app = cls;
    116   app->chat.idle = NULL;
    117 
    118   if (app->chat.quit)
    119     return;
    120 
    121   if (_chat_event(app, wgetch(app->window)))
    122   {
    123     chat_stop(&(app->chat));
    124     return;
    125   }
    126 
    127   app->chat.idle = GNUNET_SCHEDULER_add_delayed_with_priority(
    128     GNUNET_TIME_relative_multiply(
    129       GNUNET_TIME_relative_get_millisecond_(),
    130       wgetdelay(app->window)
    131     ),
    132     GNUNET_SCHEDULER_PRIORITY_IDLE,
    133     &_chat_idle,
    134     app
    135   );
    136 }
    137 
    138 void
    139 chat_start(MESSENGER_Chat *chat,
    140            struct MESSENGER_Application *app,
    141            const struct GNUNET_CONFIGURATION_Handle *cfg)
    142 {
    143   chat->handle = GNUNET_CHAT_start(
    144       cfg,
    145       &_chat_message,
    146       app
    147   );
    148 
    149   chat->context = NULL;
    150 
    151   chat->idle = GNUNET_SCHEDULER_add_now(
    152       &_chat_idle,
    153       app
    154   );
    155 
    156   chat->quit = FALSE;
    157 }
    158 
    159 void
    160 chat_stop(MESSENGER_Chat *chat)
    161 {
    162   if (chat->idle)
    163   {
    164     GNUNET_SCHEDULER_cancel(chat->idle);
    165     chat->idle = NULL;
    166   }
    167 
    168   GNUNET_CHAT_stop(chat->handle);
    169   chat->handle = NULL;
    170 
    171   chat->quit = TRUE;
    172 }
    173 
    174 void
    175 _chat_update_layout_accounts(struct MESSENGER_Application *app)
    176 {
    177   int rows, cols;
    178   getmaxyx(app->window, rows, cols);
    179 
    180   if (rows >= UTIL_LOGO_ROWS + UI_ACCOUNTS_ROWS_MIN)
    181   {
    182     const int offset = UTIL_LOGO_ROWS + 1;
    183 
    184     app->ui.logo = subwin(app->window, UTIL_LOGO_ROWS, cols, 0, 0);
    185     app->ui.main = subwin(app->window, rows - offset, cols, offset, 0);
    186 
    187     wmove(app->window, UTIL_LOGO_ROWS, 0);
    188     whline(app->window, ACS_HLINE, cols);
    189   }
    190   else
    191     app->ui.main = subwin(app->window, rows, cols, 0, 0);
    192 
    193   app->accounts.window = app->ui.main;
    194 }
    195 
    196 void
    197 _chat_update_layout_chats(struct MESSENGER_Application *app)
    198 {
    199   int rows, cols;
    200   getmaxyx(app->window, rows, cols);
    201 
    202   int min_rows = UI_CHATS_ROWS_MIN;
    203   int offset_x = 0;
    204   int offset_y = 0;
    205 
    206   if (cols >= UI_ACCOUNTS_COLS_MIN + UI_CHATS_COLS_MIN)
    207   {
    208     offset_x = UI_ACCOUNTS_COLS_MIN + 1;
    209 
    210     if (UI_ACCOUNTS_ROWS_MIN > min_rows) min_rows = UI_ACCOUNTS_ROWS_MIN;
    211   }
    212 
    213   if (rows >= UTIL_LOGO_ROWS + min_rows)
    214   {
    215     offset_y = UTIL_LOGO_ROWS + 1;
    216 
    217     app->ui.logo = subwin(app->window, UTIL_LOGO_ROWS, cols, 0, 0);
    218 
    219     wmove(app->window, UTIL_LOGO_ROWS, 0);
    220     whline(app->window, ACS_HLINE, cols);
    221   }
    222 
    223   if (offset_x > 0)
    224   {
    225     app->ui.left = subwin(
    226       app->window,
    227       rows - offset_y,
    228       UI_ACCOUNTS_COLS_MIN,
    229       offset_y,
    230       0
    231     );
    232 
    233     wmove(app->window, offset_y > 0? offset_y : 0, UI_ACCOUNTS_COLS_MIN);
    234     wvline(app->window, ACS_VLINE, rows - offset_y);
    235 
    236     if (offset_y > 0)
    237     {
    238       wmove(app->window, offset_y - 1, UI_ACCOUNTS_COLS_MIN);
    239       waddch(app->window, ACS_TTEE);
    240     }
    241   }
    242 
    243   app->ui.main = subwin(
    244       app->window,
    245       rows - offset_y,
    246       cols - offset_x,
    247       offset_y,
    248       offset_x
    249   );
    250 
    251   app->accounts.window = app->ui.left;
    252   app->chats.window = app->ui.main;
    253 }
    254 
    255 void
    256 _chat_update_layout_messages(struct MESSENGER_Application *app)
    257 {
    258   int rows, cols;
    259   getmaxyx(app->window, rows, cols);
    260 
    261   const int cols_min_left = (UTIL_LOGO_COLS > UI_CHATS_COLS_MIN?
    262       UTIL_LOGO_COLS : UI_CHATS_COLS_MIN
    263   );
    264 
    265   int offset_x, cut_x;
    266   cut_x = 0;
    267 
    268   if (cols >= cols_min_left + UI_MESSAGES_COLS_MIN)
    269     offset_x = cols_min_left + 1;
    270   else
    271   {
    272     offset_x = 0;
    273     goto skip_left_split;
    274   }
    275 
    276   if (rows >= UTIL_LOGO_ROWS + UI_CHATS_ROWS_MIN)
    277   {
    278     const int offset = UTIL_LOGO_ROWS + 1;
    279 
    280     app->ui.logo = subwin(app->window, UTIL_LOGO_ROWS, cols_min_left, 0, 0);
    281     app->ui.left = subwin(app->window, rows - offset, cols_min_left, offset, 0);
    282 
    283     wmove(app->window, UTIL_LOGO_ROWS, 0);
    284     whline(app->window, ACS_HLINE, cols_min_left);
    285   }
    286   else
    287     app->ui.left = subwin(app->window, rows, cols_min_left, 0, 0);
    288 
    289   if (cols >= cols_min_left + UI_MESSAGES_COLS_MIN + UI_MEMBERS_COLS_MIN)
    290   {
    291     cut_x = UI_MEMBERS_COLS_MIN + 1;
    292 
    293     app->ui.right = subwin(
    294 	app->window,
    295 	rows,
    296 	UI_MEMBERS_COLS_MIN,
    297 	0,
    298 	cols - UI_MEMBERS_COLS_MIN
    299     );
    300 
    301     wmove(app->window, 0, cols - cut_x);
    302     wvline(app->window, ACS_VLINE, rows);
    303   }
    304 
    305   wmove(app->window, 0, cols_min_left);
    306   wvline(app->window, ACS_VLINE, rows);
    307 
    308 skip_left_split:
    309   app->ui.main = subwin(
    310       app->window,
    311       rows,
    312       cols - offset_x - cut_x,
    313       0,
    314       offset_x
    315   );
    316 
    317   app->chats.window = app->ui.left;
    318 
    319   if (app->ui.right)
    320   {
    321     app->current.members.window = app->ui.right;
    322     app->current.messages.window = app->ui.main;
    323     return;
    324   }
    325 
    326   if (app->chat.show_members)
    327     app->current.members.window = app->ui.main;
    328   else
    329     app->current.messages.window = app->ui.main;
    330 }
    331 
    332 void
    333 chat_update_layout(MESSENGER_Chat *chat,
    334                    struct MESSENGER_Application *app)
    335 {
    336   const struct GNUNET_CHAT_Account *account = GNUNET_CHAT_get_connected(
    337       chat->handle
    338   );
    339 
    340   application_refresh(app);
    341 
    342   if (!account)
    343     _chat_update_layout_accounts(app);
    344   else if (app->chat.context)
    345     _chat_update_layout_messages(app);
    346   else
    347     _chat_update_layout_chats(app);
    348 }
    349 
    350 void
    351 chat_process_message(UNUSED MESSENGER_Chat *chat,
    352                      struct GNUNET_CHAT_Context *context,
    353                      struct GNUNET_CHAT_Message *message)
    354 {
    355   enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind(message);
    356 
    357   struct GNUNET_CHAT_Contact *sender = GNUNET_CHAT_message_get_sender(message);
    358 
    359   UI_CHAT_Handle *current = (UI_CHAT_Handle*) (
    360       GNUNET_CHAT_context_get_user_pointer(context)
    361   );
    362 
    363   if (!current)
    364     return;
    365 
    366   bool new_member = FALSE;
    367 
    368   if (GNUNET_CHAT_KIND_LEAVE == kind)
    369     members_remove(&(current->members), sender);
    370   else if (GNUNET_CHAT_KIND_JOIN == kind)
    371     new_member = members_add(&(current->members), sender);
    372 
    373   if (GNUNET_CHAT_KIND_DELETION == kind)
    374     messages_remove(
    375 	&(current->messages),
    376 	GNUNET_CHAT_message_get_target(message)
    377     );
    378   else if (GNUNET_YES == GNUNET_CHAT_message_is_deleted(message))
    379     messages_remove(&(current->messages), message);
    380   else if ((GNUNET_CHAT_KIND_JOIN != kind) || (new_member))
    381     messages_add(&(current->messages), message);
    382 }