settings.c (14937B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2021--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 ui/settings.c 23 */ 24 25 #include "settings.h" 26 27 #include "../application.h" 28 #include "../request.h" 29 #include "../ui.h" 30 31 #include "contact_entry.h" 32 #include "files.h" 33 34 #include <gnunet/gnunet_chat_lib.h> 35 #include <gnunet/gnunet_common.h> 36 37 #ifndef MESSENGER_APPLICATION_NO_PORTAL 38 #include <libportal/background.h> 39 #endif 40 41 static gboolean 42 handle_general_switch_state(UNUSED GtkSwitch *widget, 43 gboolean state, 44 gpointer user_data) 45 { 46 g_assert(user_data); 47 48 gboolean *setting = (gboolean*) user_data; 49 *setting = state; 50 return FALSE; 51 } 52 53 static void 54 _request_background_callback(MESSENGER_Application *app, 55 gboolean success, 56 gboolean error, 57 gpointer user_data) 58 { 59 g_assert((app) && (user_data)); 60 61 GtkSwitch *widget = GTK_SWITCH(user_data); 62 63 if (error) { 64 gtk_widget_set_sensitive(GTK_WIDGET(widget), !success); 65 gtk_switch_set_active(widget, success); 66 return; 67 } 68 69 gboolean *setting = (gboolean*) ( 70 g_object_get_qdata(G_OBJECT(widget), app->quarks.data) 71 ); 72 73 handle_general_switch_state(widget, success, setting); 74 } 75 76 static gboolean 77 handle_background_switch_state(GtkSwitch *widget, 78 gboolean state, 79 gpointer user_data) 80 { 81 g_assert((widget) && (user_data)); 82 83 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 84 85 gboolean *setting = (gboolean*) ( 86 g_object_get_qdata(G_OBJECT(widget), app->quarks.data) 87 ); 88 89 if ((!state) || (!gtk_widget_is_sensitive(GTK_WIDGET(widget)))) 90 return handle_general_switch_state(widget, state, setting); 91 92 XdpBackgroundFlags flags = XDP_BACKGROUND_FLAG_NONE; 93 94 if (&(app->settings.autostart) == setting) 95 flags |= XDP_BACKGROUND_FLAG_AUTOSTART; 96 if (&(app->settings.background_task) == setting) 97 flags |= XDP_BACKGROUND_FLAG_ACTIVATABLE; 98 99 request_new_background( 100 app, 101 flags, 102 _request_background_callback, 103 widget 104 ); 105 106 gtk_widget_set_sensitive(GTK_WIDGET(widget), FALSE); 107 return FALSE; 108 } 109 110 static gboolean 111 handle_inverted_switch_state(GtkSwitch *widget, 112 gboolean state, 113 gpointer user_data) 114 { 115 g_assert((widget) && (user_data)); 116 117 return handle_general_switch_state(widget, !state, user_data); 118 } 119 120 static void 121 handle_general_combo_box_change(GtkComboBox *widget, 122 gpointer user_data) 123 { 124 g_assert((widget) && (user_data)); 125 126 gulong *delay = (gulong*) user_data; 127 GtkTreeModel *model = gtk_combo_box_get_model(widget); 128 129 GtkTreeIter iter; 130 if (gtk_combo_box_get_active_iter(widget, &iter)) 131 gtk_tree_model_get(model, &iter, 1, delay, -1); 132 } 133 134 int 135 _leave_group_iteration(UNUSED void *cls, 136 UNUSED struct GNUNET_CHAT_Handle *handle, 137 struct GNUNET_CHAT_Group *group) 138 { 139 g_assert(group); 140 141 GNUNET_CHAT_group_leave(group); 142 return GNUNET_YES; 143 } 144 145 int 146 _delete_contact_iteration(UNUSED void *cls, 147 UNUSED struct GNUNET_CHAT_Handle *handle, 148 struct GNUNET_CHAT_Contact *contact) 149 { 150 g_assert(contact); 151 152 GNUNET_CHAT_contact_delete(contact); 153 return GNUNET_YES; 154 } 155 156 static void 157 handle_leave_chats_button_click(UNUSED GtkButton* button, 158 gpointer user_data) 159 { 160 g_assert(user_data); 161 162 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 163 164 application_chat_lock(app); 165 166 GNUNET_CHAT_iterate_groups( 167 app->chat.messenger.handle, 168 _leave_group_iteration, 169 NULL 170 ); 171 172 GNUNET_CHAT_iterate_contacts( 173 app->chat.messenger.handle, 174 _delete_contact_iteration, 175 NULL 176 ); 177 178 application_chat_unlock(app); 179 } 180 181 static void 182 handle_show_files_button_click(UNUSED GtkButton* button, 183 gpointer user_data) 184 { 185 g_assert(user_data); 186 187 UI_SETTINGS_Handle *handle = (UI_SETTINGS_Handle*) user_data; 188 189 handle->open_files = true; 190 191 gtk_window_close(GTK_WINDOW(handle->dialog)); 192 } 193 194 static void 195 handle_dialog_destroy(UNUSED GtkWidget *window, 196 gpointer user_data) 197 { 198 g_assert(user_data); 199 200 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 201 UI_SETTINGS_Handle *handle = &(app->ui.settings); 202 203 if (handle->open_files) 204 { 205 ui_files_dialog_init(app, &(app->ui.files)); 206 gtk_widget_show(GTK_WIDGET(app->ui.files.dialog)); 207 } 208 209 ui_settings_dialog_cleanup(handle); 210 } 211 212 static void 213 _set_combobox_to_active_by_delay(GtkComboBox *widget, 214 gulong delay) 215 { 216 g_assert(widget); 217 218 GtkTreeModel *model = gtk_combo_box_get_model(widget); 219 220 GtkTreeIter iter; 221 if (!gtk_tree_model_get_iter_first(model, &iter)) 222 return; 223 224 gulong value; 225 226 do { 227 gtk_tree_model_get(model, &iter, 1, &value, -1); 228 229 if (value == delay) 230 goto set_active; 231 232 } while (gtk_tree_model_iter_next(model, &iter)); 233 234 return; 235 set_active: 236 gtk_combo_box_set_active_iter(widget, &iter); 237 } 238 239 static enum GNUNET_GenericReturnValue 240 _count_blocked_contacts(void *cls, 241 UNUSED struct GNUNET_CHAT_Handle *handle, 242 struct GNUNET_CHAT_Contact *contact) 243 { 244 g_assert((cls) && (contact)); 245 246 if (GNUNET_YES == GNUNET_CHAT_contact_is_owned(contact)) 247 return GNUNET_YES; 248 249 guint *count = (guint*) cls; 250 251 if (GNUNET_YES == GNUNET_CHAT_contact_is_blocked(contact)) 252 *count = (*count) + 1; 253 254 return GNUNET_YES; 255 } 256 257 static enum GNUNET_GenericReturnValue 258 _iterate_blocked_contacts(void *cls, 259 UNUSED struct GNUNET_CHAT_Handle *handle, 260 struct GNUNET_CHAT_Contact *contact) 261 { 262 g_assert((cls) && (contact)); 263 264 if ((GNUNET_YES == GNUNET_CHAT_contact_is_owned(contact)) || 265 (GNUNET_YES != GNUNET_CHAT_contact_is_blocked(contact))) 266 return GNUNET_YES; 267 268 MESSENGER_Application *app = (MESSENGER_Application*) cls; 269 270 UI_CONTACT_ENTRY_Handle *entry = ui_contact_entry_new(app); 271 ui_contact_entry_set_contact(entry, contact); 272 273 gtk_list_box_prepend( 274 app->ui.settings.blocked_listbox, 275 entry->entry_box 276 ); 277 278 GtkListBoxRow *row = GTK_LIST_BOX_ROW( 279 gtk_widget_get_parent(entry->entry_box) 280 ); 281 282 g_object_set_qdata(G_OBJECT(row), app->quarks.data, contact); 283 284 g_object_set_qdata_full( 285 G_OBJECT(row), 286 app->quarks.ui, 287 entry, 288 (GDestroyNotify) ui_contact_entry_delete 289 ); 290 291 return GNUNET_YES; 292 } 293 294 void 295 ui_settings_dialog_init(MESSENGER_Application *app, 296 UI_SETTINGS_Handle *handle) 297 { 298 g_assert((app) && (handle)); 299 300 handle->builder = ui_builder_from_resource( 301 application_get_resource_path(app, "ui/settings.ui") 302 ); 303 304 handle->dialog = HDY_PREFERENCES_WINDOW( 305 gtk_builder_get_object(handle->builder, "settings_dialog") 306 ); 307 308 gtk_window_set_transient_for( 309 GTK_WINDOW(handle->dialog), 310 GTK_WINDOW(app->ui.messenger.main_window) 311 ); 312 313 handle->start_on_login_switch = GTK_SWITCH( 314 gtk_builder_get_object(handle->builder, "start_on_login_switch") 315 ); 316 317 gtk_switch_set_active( 318 handle->start_on_login_switch, 319 app->settings.autostart 320 ); 321 322 gtk_widget_set_sensitive( 323 GTK_WIDGET(handle->start_on_login_switch), 324 !(app->settings.autostart) 325 ); 326 327 g_object_set_qdata( 328 G_OBJECT(handle->start_on_login_switch), 329 app->quarks.data, 330 &(app->settings.autostart) 331 ); 332 333 g_signal_connect( 334 handle->start_on_login_switch, 335 "state-set", 336 G_CALLBACK(handle_background_switch_state), 337 app 338 ); 339 340 handle->run_in_background_switch = GTK_SWITCH( 341 gtk_builder_get_object(handle->builder, "run_in_background_switch") 342 ); 343 344 gtk_switch_set_active( 345 handle->run_in_background_switch, 346 app->settings.background_task 347 ); 348 349 gtk_widget_set_sensitive( 350 GTK_WIDGET(handle->run_in_background_switch), 351 !(app->settings.background_task) 352 ); 353 354 g_object_set_qdata( 355 G_OBJECT(handle->run_in_background_switch), 356 app->quarks.data, 357 &(app->settings.background_task) 358 ); 359 360 g_signal_connect( 361 handle->run_in_background_switch, 362 "state-set", 363 G_CALLBACK(handle_background_switch_state), 364 app 365 ); 366 367 handle->enable_notifications_switch = GTK_SWITCH( 368 gtk_builder_get_object(handle->builder, "enable_notifications_switch") 369 ); 370 371 handle->notification_sounds_switch = GTK_SWITCH( 372 gtk_builder_get_object(handle->builder, "notification_sounds_switch") 373 ); 374 375 gtk_switch_set_active( 376 handle->enable_notifications_switch, 377 !(app->settings.disable_notifications) 378 ); 379 380 gtk_switch_set_active( 381 handle->notification_sounds_switch, 382 app->settings.play_notification_sounds 383 ); 384 385 g_signal_connect( 386 handle->enable_notifications_switch, 387 "state-set", 388 G_CALLBACK(handle_inverted_switch_state), 389 &(app->settings.disable_notifications) 390 ); 391 392 g_signal_connect( 393 handle->notification_sounds_switch, 394 "state-set", 395 G_CALLBACK(handle_general_switch_state), 396 &(app->settings.play_notification_sounds) 397 ); 398 399 handle->blocked_label = GTK_LABEL( 400 gtk_builder_get_object(handle->builder, "blocked_label") 401 ); 402 403 handle->blocked_scrolled_window = GTK_WIDGET( 404 gtk_builder_get_object(handle->builder, "blocked_scrolled_window") 405 ); 406 407 handle->blocked_listbox = GTK_LIST_BOX( 408 gtk_builder_get_object(handle->builder, "blocked_listbox") 409 ); 410 411 guint blocked_count = 0; 412 GNUNET_CHAT_iterate_contacts( 413 app->chat.messenger.handle, 414 _count_blocked_contacts, 415 &blocked_count 416 ); 417 418 gtk_widget_set_size_request( 419 handle->blocked_scrolled_window, 420 0, 421 56 * (blocked_count > 3? 3 : blocked_count) 422 ); 423 424 gtk_widget_set_visible(handle->blocked_scrolled_window, blocked_count > 0); 425 426 GString *blocked_text = g_string_new(NULL); 427 if (blocked_text) 428 { 429 g_string_printf( 430 blocked_text, 431 _("%u blocked contacts"), 432 blocked_count 433 ); 434 435 gtk_label_set_text( 436 handle->blocked_label, 437 blocked_text->str 438 ); 439 440 g_string_free(blocked_text, TRUE); 441 } 442 443 GNUNET_CHAT_iterate_contacts( 444 app->chat.messenger.handle, 445 _iterate_blocked_contacts, 446 app 447 ); 448 449 gtk_list_box_invalidate_filter(handle->blocked_listbox); 450 451 handle->read_receipts_switch = GTK_SWITCH( 452 gtk_builder_get_object(handle->builder, "read_receipts_switch") 453 ); 454 455 gtk_switch_set_active( 456 handle->read_receipts_switch, 457 app->settings.send_read_receipts 458 ); 459 460 g_signal_connect( 461 handle->read_receipts_switch, 462 "state-set", 463 G_CALLBACK(handle_general_switch_state), 464 &(app->settings.send_read_receipts) 465 ); 466 467 handle->auto_delete_combo_box = GTK_COMBO_BOX( 468 gtk_builder_get_object(handle->builder, "auto_delete_combo_box") 469 ); 470 471 _set_combobox_to_active_by_delay( 472 handle->auto_delete_combo_box, 473 app->settings.auto_delete_delay 474 ); 475 476 g_signal_connect( 477 handle->auto_delete_combo_box, 478 "changed", 479 G_CALLBACK(handle_general_combo_box_change), 480 &(app->settings.auto_delete_delay) 481 ); 482 483 handle->auto_accept_invitations_switch = GTK_SWITCH( 484 gtk_builder_get_object(handle->builder, "auto_accept_invitations_switch") 485 ); 486 487 gtk_switch_set_active( 488 handle->auto_accept_invitations_switch, 489 app->settings.accept_all_invitations 490 ); 491 492 g_signal_connect( 493 handle->auto_accept_invitations_switch, 494 "state-set", 495 G_CALLBACK(handle_general_switch_state), 496 &(app->settings.accept_all_invitations) 497 ); 498 499 handle->delete_invitations_combo_box = GTK_COMBO_BOX( 500 gtk_builder_get_object(handle->builder, "delete_invitations_combo_box") 501 ); 502 503 _set_combobox_to_active_by_delay( 504 handle->delete_invitations_combo_box, 505 app->settings.delete_invitations_delay 506 ); 507 508 g_signal_connect( 509 handle->delete_invitations_combo_box, 510 "changed", 511 G_CALLBACK(handle_general_combo_box_change), 512 &(app->settings.delete_invitations_delay) 513 ); 514 515 handle->delete_invitations_button = GTK_BUTTON( 516 gtk_builder_get_object(handle->builder, "delete_invitations_button") 517 ); 518 519 handle->auto_accept_files_switch = GTK_SWITCH( 520 gtk_builder_get_object(handle->builder, "auto_accept_files_switch") 521 ); 522 523 gtk_switch_set_active( 524 handle->auto_accept_files_switch, 525 app->settings.accept_all_files 526 ); 527 528 g_signal_connect( 529 handle->auto_accept_files_switch, 530 "state-set", 531 G_CALLBACK(handle_general_switch_state), 532 &(app->settings.accept_all_files) 533 ); 534 535 handle->download_folder_button = GTK_FILE_CHOOSER_BUTTON( 536 gtk_builder_get_object(handle->builder, "download_folder_button") 537 ); 538 539 handle->delete_files_combo_box = GTK_COMBO_BOX( 540 gtk_builder_get_object(handle->builder, "delete_files_combo_box") 541 ); 542 543 _set_combobox_to_active_by_delay( 544 handle->delete_files_combo_box, 545 app->settings.delete_files_delay 546 ); 547 548 g_signal_connect( 549 handle->delete_files_combo_box, 550 "changed", 551 G_CALLBACK(handle_general_combo_box_change), 552 &(app->settings.delete_files_delay) 553 ); 554 555 handle->show_files_button = GTK_BUTTON( 556 gtk_builder_get_object(handle->builder, "show_files_button") 557 ); 558 559 g_signal_connect( 560 handle->show_files_button, 561 "clicked", 562 G_CALLBACK(handle_show_files_button_click), 563 handle 564 ); 565 566 handle->delete_files_button = GTK_BUTTON( 567 gtk_builder_get_object(handle->builder, "delete_files_button") 568 ); 569 570 handle->leave_chats_combo_box = GTK_COMBO_BOX( 571 gtk_builder_get_object(handle->builder, "leave_chats_combo_box") 572 ); 573 574 _set_combobox_to_active_by_delay( 575 handle->leave_chats_combo_box, 576 app->settings.leave_chats_delay 577 ); 578 579 g_signal_connect( 580 handle->leave_chats_combo_box, 581 "changed", 582 G_CALLBACK(handle_general_combo_box_change), 583 &(app->settings.leave_chats_delay) 584 ); 585 586 handle->leave_chats_button = GTK_BUTTON( 587 gtk_builder_get_object(handle->builder, "leave_chats_button") 588 ); 589 590 g_signal_connect( 591 handle->leave_chats_button, 592 "clicked", 593 G_CALLBACK(handle_leave_chats_button_click), 594 app 595 ); 596 597 g_signal_connect( 598 handle->dialog, 599 "destroy", 600 G_CALLBACK(handle_dialog_destroy), 601 app 602 ); 603 } 604 605 void 606 ui_settings_dialog_cleanup(UI_SETTINGS_Handle *handle) 607 { 608 g_assert(handle); 609 610 if (handle->builder) 611 g_object_unref(handle->builder); 612 613 memset(handle, 0, sizeof(*handle)); 614 }