File indexing completed on 2024-04-21 16:11:37
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Harald Sitter <sitter@kde.org> 0003 * SPDX-FileCopyrightText: 2010 Canonical Ltd. 0004 * SPDX-FileCopyrightText: 2008-2012 Red Hat Inc. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "ply-text-progress-bar.h" 0010 #include <assert.h> 0011 #include <errno.h> 0012 #include <fcntl.h> 0013 #include <math.h> 0014 #include <signal.h> 0015 #include <stdbool.h> 0016 #include <stdint.h> 0017 #include <stdio.h> 0018 #include <stdlib.h> 0019 #include <string.h> 0020 #include <sys/ioctl.h> 0021 #include <sys/stat.h> 0022 #include <sys/time.h> 0023 #include <sys/types.h> 0024 #include <termios.h> 0025 #include <unistd.h> 0026 #include <values.h> 0027 #include <wchar.h> 0028 0029 #include <ply-boot-splash-plugin.h> 0030 #include <ply-buffer.h> 0031 #include <ply-event-loop.h> 0032 #include <ply-key-file.h> 0033 #include <ply-list.h> 0034 #include <ply-logger.h> 0035 #include <ply-text-display.h> 0036 #include <ply-trigger.h> 0037 #include <ply-utils.h> 0038 0039 #include <linux/kd.h> 0040 0041 #define CLEAR_LINE_SEQUENCE "\033[2K\r\n" 0042 #define BACKSPACE "\b\033[0K" 0043 #define PLUGIN_NAME "breeze-text" 0044 0045 typedef enum { PLY_BOOT_SPLASH_DISPLAY_NORMAL, PLY_BOOT_SPLASH_DISPLAY_QUESTION_ENTRY, PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY } ply_boot_splash_display_type_t; 0046 0047 struct _ply_boot_splash_plugin { 0048 ply_event_loop_t *loop; 0049 ply_boot_splash_mode_t mode; 0050 0051 ply_list_t *views; 0052 0053 ply_boot_splash_display_type_t state; 0054 0055 char *message; 0056 0057 uint32_t is_animating : 1; 0058 uint32_t black; 0059 uint32_t white; 0060 uint32_t brown; 0061 uint32_t blue; 0062 char *title; 0063 }; 0064 0065 typedef struct { 0066 ply_boot_splash_plugin_t *plugin; 0067 ply_text_display_t *display; 0068 breeze_text_progress_bar_t *progress_bar; 0069 } view_t; 0070 0071 static void hide_splash_screen(ply_boot_splash_plugin_t *plugin, ply_event_loop_t *loop); 0072 0073 static view_t *view_new(ply_boot_splash_plugin_t *plugin, ply_text_display_t *display) 0074 { 0075 view_t *view; 0076 0077 view = calloc(1, sizeof(view_t)); 0078 view->plugin = plugin; 0079 view->display = display; 0080 0081 view->progress_bar = breeze_text_progress_bar_new(); 0082 0083 return view; 0084 } 0085 0086 static void view_free(view_t *view) 0087 { 0088 breeze_text_progress_bar_free(view->progress_bar); 0089 0090 free(view); 0091 } 0092 0093 static void view_show_message(view_t *view) 0094 { 0095 ply_boot_splash_plugin_t *plugin; 0096 int display_width, display_height, y; 0097 ply_terminal_color_t color; 0098 char *message; 0099 0100 plugin = view->plugin; 0101 0102 display_width = ply_text_display_get_number_of_columns(view->display); 0103 display_height = ply_text_display_get_number_of_rows(view->display); 0104 0105 if (!strncmp(plugin->message, "keys:", 5)) { 0106 message = plugin->message + 5; 0107 color = PLY_TERMINAL_COLOR_WHITE; 0108 y = display_height - 4; 0109 } else { 0110 message = plugin->message; 0111 color = PLY_TERMINAL_COLOR_BLUE; 0112 y = display_height / 2 + 7; 0113 } 0114 0115 ply_text_display_set_cursor_position(view->display, 0, y); 0116 ply_text_display_clear_line(view->display); 0117 ply_text_display_set_cursor_position(view->display, (display_width - strlen(message)) / 2, y); 0118 0119 ply_text_display_set_foreground_color(view->display, color); 0120 ply_text_display_write(view->display, "%s", message); 0121 } 0122 0123 static void view_show_prompt(view_t *view, const char *prompt, const char *entered_text) 0124 { 0125 int display_width, display_height; 0126 0127 display_width = ply_text_display_get_number_of_columns(view->display); 0128 display_height = ply_text_display_get_number_of_rows(view->display); 0129 0130 ply_text_display_set_cursor_position(view->display, 0, display_height / 2 + 8); 0131 ply_text_display_clear_line(view->display); 0132 ply_text_display_set_cursor_position(view->display, display_width / 2 - (strlen(prompt)), display_height / 2 + 8); 0133 0134 ply_text_display_write(view->display, "%s:%s", prompt, entered_text); 0135 0136 ply_text_display_show_cursor(view->display); 0137 } 0138 0139 static void view_start_animation(view_t *view) 0140 { 0141 ply_boot_splash_plugin_t *plugin; 0142 ply_terminal_t *terminal; 0143 0144 assert(view != NULL); 0145 0146 plugin = view->plugin; 0147 0148 terminal = ply_text_display_get_terminal(view->display); 0149 0150 ply_terminal_set_color_hex_value(terminal, PLY_TERMINAL_COLOR_BLACK, plugin->black); 0151 ply_terminal_set_color_hex_value(terminal, PLY_TERMINAL_COLOR_WHITE, plugin->white); 0152 ply_terminal_set_color_hex_value(terminal, PLY_TERMINAL_COLOR_BLUE, plugin->blue); 0153 ply_terminal_set_color_hex_value(terminal, PLY_TERMINAL_COLOR_BROWN, plugin->brown); 0154 0155 ply_text_display_set_background_color(view->display, PLY_TERMINAL_COLOR_BLACK); 0156 ply_text_display_clear_screen(view->display); 0157 ply_text_display_hide_cursor(view->display); 0158 0159 if (plugin->mode == PLY_BOOT_SPLASH_MODE_SHUTDOWN) { 0160 breeze_text_progress_bar_hide(view->progress_bar); 0161 return; 0162 } 0163 0164 breeze_text_progress_bar_show(view->progress_bar, view->display); 0165 } 0166 0167 static void view_redraw(view_t *view) 0168 { 0169 unsigned long screen_width, screen_height; 0170 0171 screen_width = ply_text_display_get_number_of_columns(view->display); 0172 screen_height = ply_text_display_get_number_of_rows(view->display); 0173 0174 ply_text_display_draw_area(view->display, 0, 0, screen_width, screen_height); 0175 } 0176 0177 static void redraw_views(ply_boot_splash_plugin_t *plugin) 0178 { 0179 ply_list_node_t *node; 0180 0181 node = ply_list_get_first_node(plugin->views); 0182 while (node != NULL) { 0183 ply_list_node_t *next_node; 0184 view_t *view; 0185 0186 view = ply_list_node_get_data(node); 0187 next_node = ply_list_get_next_node(plugin->views, node); 0188 0189 view_redraw(view); 0190 0191 node = next_node; 0192 } 0193 } 0194 0195 static void view_hide(view_t *view) 0196 { 0197 if (view->display != NULL) { 0198 ply_terminal_t *terminal; 0199 0200 terminal = ply_text_display_get_terminal(view->display); 0201 0202 ply_text_display_set_background_color(view->display, PLY_TERMINAL_COLOR_DEFAULT); 0203 ply_text_display_clear_screen(view->display); 0204 ply_text_display_show_cursor(view->display); 0205 0206 ply_terminal_reset_colors(terminal); 0207 } 0208 } 0209 0210 static void hide_views(ply_boot_splash_plugin_t *plugin) 0211 { 0212 ply_list_node_t *node; 0213 0214 node = ply_list_get_first_node(plugin->views); 0215 while (node != NULL) { 0216 ply_list_node_t *next_node; 0217 view_t *view; 0218 0219 view = ply_list_node_get_data(node); 0220 next_node = ply_list_get_next_node(plugin->views, node); 0221 0222 view_hide(view); 0223 0224 node = next_node; 0225 } 0226 } 0227 0228 static void pause_views(ply_boot_splash_plugin_t *plugin) 0229 { 0230 ply_list_node_t *node; 0231 0232 node = ply_list_get_first_node(plugin->views); 0233 while (node != NULL) { 0234 ply_list_node_t *next_node; 0235 view_t *view; 0236 0237 view = ply_list_node_get_data(node); 0238 next_node = ply_list_get_next_node(plugin->views, node); 0239 0240 ply_text_display_pause_updates(view->display); 0241 0242 node = next_node; 0243 } 0244 } 0245 0246 static void unpause_views(ply_boot_splash_plugin_t *plugin) 0247 { 0248 ply_list_node_t *node; 0249 0250 node = ply_list_get_first_node(plugin->views); 0251 while (node != NULL) { 0252 ply_list_node_t *next_node; 0253 view_t *view; 0254 0255 view = ply_list_node_get_data(node); 0256 next_node = ply_list_get_next_node(plugin->views, node); 0257 0258 ply_text_display_unpause_updates(view->display); 0259 0260 node = next_node; 0261 } 0262 } 0263 0264 static ply_boot_splash_plugin_t *create_plugin(ply_key_file_t *key_file) 0265 { 0266 char *option; 0267 0268 ply_boot_splash_plugin_t *plugin; 0269 0270 ply_trace("creating plugin"); 0271 0272 plugin = calloc(1, sizeof(ply_boot_splash_plugin_t)); 0273 plugin->message = NULL; 0274 0275 plugin->views = ply_list_new(); 0276 0277 /* Not a pretty API for setting defaults for your config file... */ 0278 plugin->black = 0x000000; 0279 plugin->white = 0xeff0f1; // progress bar block 1 0280 plugin->blue = 0xeff0f1; // progress bar block 2 0281 plugin->brown = 0xeff0f1; // progress bar block 3 0282 0283 option = ply_key_file_get_value(key_file, PLUGIN_NAME, "black"); 0284 if (option) 0285 sscanf(option, "0x%x", &plugin->black); 0286 option = ply_key_file_get_value(key_file, PLUGIN_NAME, "white"); 0287 if (option) 0288 sscanf(option, "0x%x", &plugin->white); 0289 option = ply_key_file_get_value(key_file, PLUGIN_NAME, "brown"); 0290 if (option) 0291 sscanf(option, "0x%x", &plugin->brown); 0292 option = ply_key_file_get_value(key_file, PLUGIN_NAME, "blue"); 0293 if (option) 0294 sscanf(option, "0x%x", &plugin->blue); 0295 0296 plugin->title = ply_key_file_get_value(key_file, PLUGIN_NAME, "title"); 0297 0298 return plugin; 0299 } 0300 0301 static void detach_from_event_loop(ply_boot_splash_plugin_t *plugin) 0302 { 0303 plugin->loop = NULL; 0304 0305 ply_trace("detaching from event loop"); 0306 } 0307 0308 static void free_views(ply_boot_splash_plugin_t *plugin) 0309 { 0310 ply_list_node_t *node; 0311 0312 node = ply_list_get_first_node(plugin->views); 0313 0314 while (node != NULL) { 0315 ply_list_node_t *next_node; 0316 view_t *view; 0317 0318 view = ply_list_node_get_data(node); 0319 next_node = ply_list_get_next_node(plugin->views, node); 0320 0321 view_free(view); 0322 ply_list_remove_node(plugin->views, node); 0323 0324 node = next_node; 0325 } 0326 0327 ply_list_free(plugin->views); 0328 plugin->views = NULL; 0329 } 0330 0331 static void destroy_plugin(ply_boot_splash_plugin_t *plugin) 0332 { 0333 ply_trace("destroying plugin"); 0334 0335 if (plugin == NULL) 0336 return; 0337 0338 /* It doesn't ever make sense to keep this plugin on screen 0339 * after exit 0340 */ 0341 hide_splash_screen(plugin, plugin->loop); 0342 0343 free_views(plugin); 0344 if (plugin->message != NULL) 0345 free(plugin->message); 0346 0347 free(plugin); 0348 } 0349 0350 static void show_message(ply_boot_splash_plugin_t *plugin) 0351 { 0352 ply_list_node_t *node; 0353 0354 node = ply_list_get_first_node(plugin->views); 0355 while (node != NULL) { 0356 ply_list_node_t *next_node; 0357 view_t *view; 0358 0359 view = ply_list_node_get_data(node); 0360 next_node = ply_list_get_next_node(plugin->views, node); 0361 0362 view_show_message(view); 0363 0364 node = next_node; 0365 } 0366 } 0367 0368 static void start_animation(ply_boot_splash_plugin_t *plugin) 0369 { 0370 ply_list_node_t *node; 0371 0372 assert(plugin != NULL); 0373 assert(plugin->loop != NULL); 0374 0375 redraw_views(plugin); 0376 0377 if (plugin->message != NULL) 0378 show_message(plugin); 0379 0380 if (plugin->is_animating) 0381 return; 0382 0383 node = ply_list_get_first_node(plugin->views); 0384 while (node != NULL) { 0385 ply_list_node_t *next_node; 0386 view_t *view; 0387 0388 view = ply_list_node_get_data(node); 0389 next_node = ply_list_get_next_node(plugin->views, node); 0390 0391 view_start_animation(view); 0392 0393 node = next_node; 0394 } 0395 0396 plugin->is_animating = true; 0397 } 0398 0399 static void stop_animation(ply_boot_splash_plugin_t *plugin) 0400 { 0401 ply_list_node_t *node; 0402 0403 assert(plugin != NULL); 0404 assert(plugin->loop != NULL); 0405 0406 if (!plugin->is_animating) 0407 return; 0408 0409 plugin->is_animating = false; 0410 0411 node = ply_list_get_first_node(plugin->views); 0412 while (node != NULL) { 0413 ply_list_node_t *next_node; 0414 view_t *view; 0415 0416 view = ply_list_node_get_data(node); 0417 next_node = ply_list_get_next_node(plugin->views, node); 0418 0419 breeze_text_progress_bar_hide(view->progress_bar); 0420 0421 node = next_node; 0422 } 0423 redraw_views(plugin); 0424 } 0425 0426 static void on_draw(view_t *view, ply_terminal_t *terminal, int x, int y, int width, int height) 0427 { 0428 ply_text_display_clear_screen(view->display); 0429 } 0430 0431 static void add_text_display(ply_boot_splash_plugin_t *plugin, ply_text_display_t *display) 0432 { 0433 view_t *view; 0434 ply_terminal_t *terminal; 0435 0436 view = view_new(plugin, display); 0437 0438 terminal = ply_text_display_get_terminal(view->display); 0439 if (ply_terminal_open(terminal)) { 0440 ply_terminal_set_mode(terminal, PLY_TERMINAL_MODE_TEXT); 0441 ply_terminal_activate_vt(terminal); 0442 } 0443 0444 ply_text_display_set_draw_handler(view->display, (ply_text_display_draw_handler_t)on_draw, view); 0445 0446 ply_list_append_data(plugin->views, view); 0447 } 0448 0449 static void remove_text_display(ply_boot_splash_plugin_t *plugin, ply_text_display_t *display) 0450 { 0451 ply_list_node_t *node; 0452 0453 node = ply_list_get_first_node(plugin->views); 0454 while (node != NULL) { 0455 view_t *view; 0456 ply_list_node_t *next_node; 0457 0458 view = ply_list_node_get_data(node); 0459 next_node = ply_list_get_next_node(plugin->views, node); 0460 0461 if (view->display == display) { 0462 ply_text_display_set_draw_handler(view->display, NULL, NULL); 0463 view_free(view); 0464 ply_list_remove_node(plugin->views, node); 0465 return; 0466 } 0467 0468 node = next_node; 0469 } 0470 } 0471 0472 static bool show_splash_screen(ply_boot_splash_plugin_t *plugin, ply_event_loop_t *loop, ply_buffer_t *boot_buffer, ply_boot_splash_mode_t mode) 0473 { 0474 assert(plugin != NULL); 0475 0476 plugin->loop = loop; 0477 plugin->mode = mode; 0478 ply_event_loop_watch_for_exit(loop, (ply_event_loop_exit_handler_t)detach_from_event_loop, plugin); 0479 0480 ply_show_new_kernel_messages(false); 0481 start_animation(plugin); 0482 0483 return true; 0484 } 0485 0486 static void update_status(ply_boot_splash_plugin_t *plugin, const char *status) 0487 { 0488 assert(plugin != NULL); 0489 0490 ply_trace("status update"); 0491 } 0492 0493 static void on_boot_progress(ply_boot_splash_plugin_t *plugin, double duration, double percent_done) 0494 { 0495 ply_list_node_t *node; 0496 double total_duration; 0497 0498 total_duration = duration / percent_done; 0499 0500 /* Fun made-up smoothing function to make the growth asymptotic: 0501 * fraction(time,estimate)=1-2^(-(time^1.45)/estimate) */ 0502 percent_done = 1.0 - pow(2.0, -pow(duration, 1.45) / total_duration) * (1.0 - percent_done); 0503 0504 node = ply_list_get_first_node(plugin->views); 0505 0506 while (node != NULL) { 0507 ply_list_node_t *next_node; 0508 view_t *view; 0509 0510 view = ply_list_node_get_data(node); 0511 next_node = ply_list_get_next_node(plugin->views, node); 0512 0513 { 0514 int display_width = ply_text_display_get_number_of_columns(view->display); 0515 int display_height = ply_text_display_get_number_of_rows(view->display); 0516 0517 ply_text_display_set_cursor_position(view->display, (display_width - 12) / 2, display_height / 2); 0518 0519 ply_text_display_set_background_color(view->display, PLY_TERMINAL_COLOR_BLACK); 0520 ply_text_display_set_foreground_color(view->display, PLY_TERMINAL_COLOR_WHITE); 0521 ply_text_display_write(view->display, "%s", plugin->title); 0522 } 0523 0524 breeze_text_progress_bar_set_percent_done(view->progress_bar, percent_done); 0525 breeze_text_progress_bar_draw(view->progress_bar); 0526 0527 node = next_node; 0528 } 0529 } 0530 0531 static void hide_splash_screen(ply_boot_splash_plugin_t *plugin, ply_event_loop_t *loop) 0532 { 0533 assert(plugin != NULL); 0534 0535 ply_trace("hiding splash screen"); 0536 0537 if (plugin->loop != NULL) { 0538 stop_animation(plugin); 0539 0540 ply_event_loop_stop_watching_for_exit(plugin->loop, (ply_event_loop_exit_handler_t)detach_from_event_loop, plugin); 0541 detach_from_event_loop(plugin); 0542 } 0543 0544 hide_views(plugin); 0545 ply_show_new_kernel_messages(true); 0546 } 0547 0548 static void display_normal(ply_boot_splash_plugin_t *plugin) 0549 { 0550 pause_views(plugin); 0551 if (plugin->state != PLY_BOOT_SPLASH_DISPLAY_NORMAL) { 0552 plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; 0553 start_animation(plugin); 0554 redraw_views(plugin); 0555 } 0556 unpause_views(plugin); 0557 } 0558 0559 static void display_message(ply_boot_splash_plugin_t *plugin, const char *message) 0560 { 0561 if (plugin->message != NULL) 0562 free(plugin->message); 0563 0564 plugin->message = strdup(message); 0565 start_animation(plugin); 0566 } 0567 0568 static void show_password_prompt(ply_boot_splash_plugin_t *plugin, const char *prompt, int bullets) 0569 { 0570 ply_list_node_t *node; 0571 int i; 0572 char *entered_text; 0573 0574 entered_text = calloc(bullets + 1, sizeof(char)); 0575 0576 for (i = 0; i < bullets; i++) 0577 entered_text[i] = '*'; 0578 0579 node = ply_list_get_first_node(plugin->views); 0580 while (node != NULL) { 0581 ply_list_node_t *next_node; 0582 view_t *view; 0583 0584 view = ply_list_node_get_data(node); 0585 next_node = ply_list_get_next_node(plugin->views, node); 0586 0587 view_show_prompt(view, prompt, entered_text); 0588 0589 node = next_node; 0590 } 0591 free(entered_text); 0592 } 0593 0594 static void show_prompt(ply_boot_splash_plugin_t *plugin, const char *prompt, const char *text) 0595 { 0596 ply_list_node_t *node; 0597 0598 node = ply_list_get_first_node(plugin->views); 0599 while (node != NULL) { 0600 ply_list_node_t *next_node; 0601 view_t *view; 0602 0603 view = ply_list_node_get_data(node); 0604 next_node = ply_list_get_next_node(plugin->views, node); 0605 0606 view_show_prompt(view, prompt, text); 0607 0608 node = next_node; 0609 } 0610 } 0611 0612 static void display_password(ply_boot_splash_plugin_t *plugin, const char *prompt, int bullets) 0613 { 0614 pause_views(plugin); 0615 if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL) 0616 stop_animation(plugin); 0617 0618 plugin->state = PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY; 0619 0620 if (!prompt) 0621 prompt = "Password"; 0622 0623 show_password_prompt(plugin, prompt, bullets); 0624 0625 unpause_views(plugin); 0626 } 0627 0628 static void display_question(ply_boot_splash_plugin_t *plugin, const char *prompt, const char *entry_text) 0629 { 0630 pause_views(plugin); 0631 if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL) 0632 stop_animation(plugin); 0633 0634 plugin->state = PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY; 0635 0636 if (!prompt) 0637 prompt = "Password"; 0638 0639 show_prompt(plugin, prompt, entry_text); 0640 0641 unpause_views(plugin); 0642 } 0643 0644 ply_boot_splash_plugin_interface_t *ply_boot_splash_plugin_get_interface(void) 0645 { 0646 static ply_boot_splash_plugin_interface_t plugin_interface = { 0647 .create_plugin = create_plugin, 0648 .destroy_plugin = destroy_plugin, 0649 .add_text_display = add_text_display, 0650 .remove_text_display = remove_text_display, 0651 .show_splash_screen = show_splash_screen, 0652 .update_status = update_status, 0653 .on_boot_progress = on_boot_progress, 0654 .hide_splash_screen = hide_splash_screen, 0655 .display_normal = display_normal, 0656 .display_message = display_message, 0657 .display_password = display_password, 0658 .display_question = display_question, 0659 }; 0660 0661 return &plugin_interface; 0662 } 0663 0664 /* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */