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 }