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: */