You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-overlay/sys-boot/plymouth-calculate-plugin/files/0.9.5-plugin.c

2224 lines
89 KiB

/*
*
* Copyright (C) 2009-2019 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by: William Jon McCann, Hans de Goede <hdegoede@redhat.com>
*
*/
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <values.h>
#include <unistd.h>
#include <wchar.h>
#include <dirent.h>
#include "ply-boot-splash-plugin.h"
#include "ply-buffer.h"
#include "ply-capslock-icon.h"
#include "ply-entry.h"
#include "ply-event-loop.h"
#include "ply-label.h"
#include "ply-list.h"
#include "ply-logger.h"
#include "ply-image.h"
#include "ply-key-file.h"
#include "ply-keymap-icon.h"
#include "ply-trigger.h"
#include "ply-pixel-buffer.h"
#include "ply-pixel-display.h"
#include "ply-utils.h"
#include "ply-i18n.h"
#include "ply-animation.h"
#include "ply-progress-animation.h"
#include "ply-throbber.h"
#include "ply-progress-bar.h"
#include <linux/kd.h>
#ifndef FRAMES_PER_SECOND
#define FRAMES_PER_SECOND 30
#endif
#ifndef SHOW_ANIMATION_FRACTION
#define SHOW_ANIMATION_FRACTION 0.9
#endif
#define PROGRESS_BAR_WIDTH 400
#define PROGRESS_BAR_HEIGHT 5
#define BGRT_STATUS_ORIENTATION_OFFSET_0 (0 << 1)
#define BGRT_STATUS_ORIENTATION_OFFSET_90 (1 << 1)
#define BGRT_STATUS_ORIENTATION_OFFSET_180 (2 << 1)
#define BGRT_STATUS_ORIENTATION_OFFSET_270 (3 << 1)
#define BGRT_STATUS_ORIENTATION_OFFSET_MASK (3 << 1)
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;
typedef enum
{
PROGRESS_FUNCTION_TYPE_WWOODS,
PROGRESS_FUNCTION_TYPE_LINEAR,
} progress_function_t;
typedef struct
{
ply_boot_splash_plugin_t *plugin;
ply_pixel_display_t *display;
ply_entry_t *entry;
ply_keymap_icon_t *keymap_icon;
ply_capslock_icon_t *capslock_icon;
ply_animation_t *end_animation;
ply_progress_animation_t *progress_animation;
ply_progress_bar_t *progress_bar;
ply_throbber_t *throbber;
ply_label_t *label;
ply_label_t *message_label;
ply_label_t *title_label;
ply_label_t *subtitle_label;
ply_rectangle_t box_area, lock_area, watermark_area, title_area, dialog_area;
ply_trigger_t *end_trigger;
ply_pixel_buffer_t *background_buffer;
ply_image_t *watermark_image;
int animation_bottom;
} view_t;
typedef struct
{
bool suppress_messages;
bool progress_bar_show_percent_complete;
bool use_progress_bar;
bool use_animation;
bool use_end_animation;
bool use_firmware_background;
char *title;
char *subtitle;
uint32_t background_start_color;
uint32_t background_end_color;
uint32_t title_color;
char *watermark_imagename;
double animation_horizontal_alignment;
double animation_vertical_alignment;
char *animation_vertical_alignment_type;
} mode_settings_t;
struct _ply_boot_splash_plugin
{
ply_event_loop_t *loop;
ply_boot_splash_mode_t mode;
mode_settings_t mode_settings[PLY_BOOT_SPLASH_MODE_COUNT];
char *font;
ply_image_t *lock_image;
ply_image_t *box_image;
ply_image_t *corner_image;
ply_image_t *header_image;
ply_image_t *background_tile_image;
ply_image_t *background_bgrt_image;
ply_list_t *views;
ply_boot_splash_display_type_t state;
double dialog_horizontal_alignment;
double dialog_vertical_alignment;
double title_horizontal_alignment;
double title_vertical_alignment;
char *title_font;
double watermark_horizontal_alignment;
double watermark_vertical_alignment;
char *watermark_imagename;
double animation_horizontal_alignment;
double animation_vertical_alignment;
char *animation_vertical_alignment_type;
char *animation_dir;
ply_progress_animation_transition_t transition;
double transition_duration;
uint32_t background_start_color;
uint32_t background_end_color;
uint32_t title_color;
int background_bgrt_raw_width;
int background_bgrt_raw_height;
double progress_bar_horizontal_alignment;
double progress_bar_vertical_alignment;
long progress_bar_width;
long progress_bar_height;
uint32_t progress_bar_bg_color;
uint32_t progress_bar_fg_color;
progress_function_t progress_function;
ply_trigger_t *idle_trigger;
ply_trigger_t *stop_trigger;
uint32_t root_is_mounted : 1;
uint32_t is_visible : 1;
uint32_t is_animating : 1;
uint32_t is_idle : 1;
uint32_t use_firmware_background : 1;
uint32_t dialog_clears_firmware_background : 1;
uint32_t message_below_animation : 1;
};
ply_boot_splash_plugin_interface_t *ply_boot_splash_plugin_get_interface (void);
static void stop_animation (ply_boot_splash_plugin_t *plugin);
static void detach_from_event_loop (ply_boot_splash_plugin_t *plugin);
static void display_message (ply_boot_splash_plugin_t *plugin,
const char *message);
static void become_idle (ply_boot_splash_plugin_t *plugin,
ply_trigger_t *idle_trigger);
static void view_show_message (view_t *view, const char *message);
bool is_dir(const char* path) {
struct stat path_stat;
stat(path, &path_stat);
return S_ISDIR(path_stat.st_mode);
}
void image_get_res(const char *basedir, int *xres, int *yres)
{
FILE *fp;
char buf[512];
int oxres, oyres;
oxres = *xres;
oyres = *yres;
snprintf(buf, 512, "%s/%dx%d.png", basedir, oxres, oyres);
fp = fopen(buf, "r");
if (!fp) {
*xres = 1024;
*yres = 768;
unsigned int t, tx, ty, mdist = 0xffffffff;
struct dirent *dent;
DIR *tdir;
snprintf(buf, 512, "%s", basedir);
tdir = opendir(buf);
if (!tdir) {
*xres = 0;
*yres = 0;
return;
}
while ((dent = readdir(tdir))) {
if (sscanf(dent->d_name, "%dx%d.png", &tx, &ty) != 2)
continue;
/* We only want configs for resolutions smaller than the current one,
* so that we can actually fit the image on the screen. */
if (tx >= oxres || ty >= oyres)
continue;
t = (tx - oxres) * (tx - oxres) + (ty - oyres) * (ty - oyres);
/* Penalize configs for resolutions with different aspect ratios. */
if (oxres / oyres != tx / ty)
t *= 10;
if (t < mdist) {
*xres = tx;
*yres = ty;
mdist = t;
}
}
closedir(tdir);
} else {
fclose(fp);
}
}
char *detect_image(const char *logo_image, int xres, int yres) {
char *buf;
if(logo_image != NULL) {
if(is_dir(logo_image)) {
image_get_res(logo_image, &xres, &yres);
if(asprintf(&buf, "%s/%dx%d.png", logo_image, xres, yres) != -1)
return buf;
} else {
return strdup (logo_image);
}
}
return NULL;
}
long
calculate_animation_y(view_t *view, double animation_vertical_alignment,
char *animation_vertical_alignment_type, unsigned long screen_height, long height)
{
ply_boot_splash_plugin_t *plugin;
plugin = view->plugin;
long y;
if (animation_vertical_alignment_type == NULL) {
y = animation_vertical_alignment * screen_height - height / 2.0;
} else if (strcmp(animation_vertical_alignment_type,
"below_title") == 0) {
y = view->title_area.y + view->title_area.height;
if( y + height >= screen_height) {
y = animation_vertical_alignment * screen_height - height / 2.0;
} else {
y += animation_vertical_alignment * (screen_height - y);
if( animation_vertical_alignment != 0.0) {
y -= height / 2;
}
}
} else if (strcmp(animation_vertical_alignment_type,
"below_watermark") == 0 && view->watermark_image != NULL) {
y = view->watermark_area.y + view->watermark_area.height;
if( y + height >= screen_height) {
y = animation_vertical_alignment * screen_height - height / 2.0;
} else {
y += animation_vertical_alignment * (screen_height - y);
if( animation_vertical_alignment != 0.0) {
y -= height / 2;
}
}
} else {
y = animation_vertical_alignment * screen_height - height / 2.0;
}
return y;
}
static view_t *
view_new (ply_boot_splash_plugin_t *plugin,
ply_pixel_display_t *display)
{
view_t *view;
view = calloc (1, sizeof(view_t));
view->plugin = plugin;
view->display = display;
view->watermark_image = NULL;
view->entry = ply_entry_new (plugin->animation_dir);
view->keymap_icon = ply_keymap_icon_new (display, plugin->animation_dir);
view->capslock_icon = ply_capslock_icon_new (plugin->animation_dir);
view->progress_animation = ply_progress_animation_new (plugin->animation_dir,
"progress-");
ply_progress_animation_set_transition (view->progress_animation,
plugin->transition,
plugin->transition_duration);
view->progress_bar = ply_progress_bar_new ();
ply_progress_bar_set_colors (view->progress_bar,
plugin->progress_bar_fg_color,
plugin->progress_bar_bg_color);
view->throbber = ply_throbber_new (plugin->animation_dir,
"throbber-");
view->label = ply_label_new ();
ply_label_set_font (view->label, plugin->font);
view->message_label = ply_label_new ();
ply_label_set_font (view->message_label, plugin->font);
view->title_label = ply_label_new ();
ply_label_set_font (view->title_label, plugin->title_font);
view->subtitle_label = ply_label_new ();
ply_label_set_font (view->subtitle_label, plugin->font);
return view;
}
static void
view_free (view_t *view)
{
ply_entry_free (view->entry);
ply_keymap_icon_free (view->keymap_icon);
ply_capslock_icon_free (view->capslock_icon);
ply_animation_free (view->end_animation);
ply_progress_animation_free (view->progress_animation);
ply_progress_bar_free (view->progress_bar);
ply_throbber_free (view->throbber);
ply_label_free (view->label);
ply_label_free (view->message_label);
ply_label_free (view->title_label);
ply_label_free (view->subtitle_label);
if (view->watermark_image != NULL)
ply_image_free (view->watermark_image);
if (view->background_buffer != NULL)
ply_pixel_buffer_free (view->background_buffer);
free (view);
}
static void
view_load_end_animation (view_t *view)
{
ply_boot_splash_plugin_t *plugin = view->plugin;
const char *animation_prefix;
if (!plugin->mode_settings[plugin->mode].use_end_animation)
return;
ply_trace ("loading animation");
switch (plugin->mode) {
case PLY_BOOT_SPLASH_MODE_BOOT_UP:
case PLY_BOOT_SPLASH_MODE_UPDATES:
case PLY_BOOT_SPLASH_MODE_SYSTEM_UPGRADE:
case PLY_BOOT_SPLASH_MODE_FIRMWARE_UPGRADE:
animation_prefix = "startup-animation-";
break;
case PLY_BOOT_SPLASH_MODE_SHUTDOWN:
case PLY_BOOT_SPLASH_MODE_REBOOT:
animation_prefix = "shutdown-animation-";
break;
case PLY_BOOT_SPLASH_MODE_INVALID:
default:
ply_trace ("unexpected splash mode 0x%x\n", plugin->mode);
return;
}
ply_trace ("trying prefix: %s", animation_prefix);
view->end_animation = ply_animation_new (plugin->animation_dir,
animation_prefix);
if (ply_animation_load (view->end_animation))
return;
ply_animation_free (view->end_animation);
ply_trace ("now trying more general prefix: animation-");
view->end_animation = ply_animation_new (plugin->animation_dir,
"animation-");
if (ply_animation_load (view->end_animation))
return;
ply_animation_free (view->end_animation);
ply_trace ("now trying old compat prefix: throbber-");
view->end_animation = ply_animation_new (plugin->animation_dir,
"throbber-");
if (ply_animation_load (view->end_animation)) {
/* files named throbber- are for end animation, so
* there's no throbber */
ply_throbber_free (view->throbber);
view->throbber = NULL;
return;
}
ply_trace ("optional animation didn't load");
ply_animation_free (view->end_animation);
view->end_animation = NULL;
plugin->mode_settings[plugin->mode].use_end_animation = false;
}
static bool
get_bgrt_sysfs_info(int *x_offset, int *y_offset,
ply_pixel_buffer_rotation_t *rotation)
{
bool ret = false;
char buf[64];
int status;
FILE *f;
f = fopen("/sys/firmware/acpi/bgrt/status", "r");
if (!f)
return false;
if (!fgets(buf, sizeof(buf), f))
goto out;
if (sscanf(buf, "%d", &status) != 1)
goto out;
fclose(f);
switch (status & BGRT_STATUS_ORIENTATION_OFFSET_MASK) {
case BGRT_STATUS_ORIENTATION_OFFSET_0:
*rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
break;
case BGRT_STATUS_ORIENTATION_OFFSET_90:
*rotation = PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE;
break;
case BGRT_STATUS_ORIENTATION_OFFSET_180:
*rotation = PLY_PIXEL_BUFFER_ROTATE_UPSIDE_DOWN;
break;
case BGRT_STATUS_ORIENTATION_OFFSET_270:
*rotation = PLY_PIXEL_BUFFER_ROTATE_CLOCKWISE;
break;
}
f = fopen("/sys/firmware/acpi/bgrt/xoffset", "r");
if (!f)
return false;
if (!fgets(buf, sizeof(buf), f))
goto out;
if (sscanf(buf, "%d", x_offset) != 1)
goto out;
fclose(f);
f = fopen("/sys/firmware/acpi/bgrt/yoffset", "r");
if (!f)
return false;
if (!fgets(buf, sizeof(buf), f))
goto out;
if (sscanf(buf, "%d", y_offset) != 1)
goto out;
ret = true;
out:
fclose(f);
return ret;
}
/* The Microsoft boot logo spec says that the logo must use a black background
* and have its center at 38.2% from the screen's top (golden ratio).
* We reproduce this exactly here so that we get a background which is an exact
* match of the firmware's boot splash.
* At the time of writing this comment this is documented in a document called
* "Boot screen components" which is available here:
* https://docs.microsoft.com/en-us/windows-hardware/drivers/bringup/boot-screen-components
* Note that we normally do not use the firmware reported x and y-offset as
* that is based on the EFI fb resolution which may not be the native
* resolution of the screen (esp. when using multiple heads).
*/
static void
view_set_bgrt_background (view_t *view)
{
ply_pixel_buffer_rotation_t panel_rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
ply_pixel_buffer_rotation_t bgrt_rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
int x_offset, y_offset, sysfs_x_offset, sysfs_y_offset, width, height;
int panel_width = 0, panel_height = 0, panel_scale = 1;
int screen_width, screen_height, screen_scale;
ply_pixel_buffer_t *bgrt_buffer;
bool have_panel_props;
if (!view->plugin->background_bgrt_image)
return;
if (!get_bgrt_sysfs_info(&sysfs_x_offset, &sysfs_y_offset,
&bgrt_rotation)) {
ply_trace ("get bgrt sysfs info failed");
return;
}
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
screen_scale = ply_pixel_display_get_device_scale (view->display);
bgrt_buffer = ply_image_get_buffer (view->plugin->background_bgrt_image);
have_panel_props = ply_renderer_get_panel_properties (ply_pixel_display_get_renderer (view->display),
&panel_width, &panel_height,
&panel_rotation, &panel_scale);
/*
* Some buggy Lenovo 2-in-1s with a 90 degree rotated panel, behave as
* if the panel is mounted up-right / not rotated at all. These devices
* have a buggy efifb size (landscape resolution instead of the actual
* portrait resolution of the panel), this gets fixed-up by the kernel.
* These buggy devices also do not pre-rotate the bgrt_image nor do
* they set the ACPI-6.2 rotation status-bits. We can detect this by
* checking that the bgrt_image is perfectly centered horizontally
* when we use the panel's height as the width.
*/
if (have_panel_props &&
(panel_rotation == PLY_PIXEL_BUFFER_ROTATE_CLOCKWISE ||
panel_rotation == PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE) &&
(panel_width - view->plugin->background_bgrt_raw_width) / 2 != sysfs_x_offset &&
(panel_height - view->plugin->background_bgrt_raw_width) / 2 == sysfs_x_offset)
bgrt_rotation = panel_rotation;
/*
* Before the ACPI 6.2 specification, the BGRT table did not contain
* any rotation information, so to make sure that the firmware-splash
* showed the right way up the firmware would contain a pre-rotated
* image. Starting with ACPI 6.2 the bgrt status fields has 2 bits
* to tell the firmware the image needs to be rotated before being
* displayed.
* If these bits are set then the firmwares-splash is not pre-rotated,
* in this case we must not rotate it when rendering and when doing
* comparisons with the panel-size we must use the post rotation
* panel-size.
*/
if (bgrt_rotation != PLY_PIXEL_BUFFER_ROTATE_UPRIGHT) {
if (bgrt_rotation != panel_rotation) {
ply_trace ("bgrt orientation mismatch, bgrt_rot %d panel_rot %d", (int)bgrt_rotation, (int)panel_rotation);
return;
}
/* Set panel properties to their post-rotations values */
if (panel_rotation == PLY_PIXEL_BUFFER_ROTATE_CLOCKWISE ||
panel_rotation == PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE) {
int temp = panel_width;
panel_width = panel_height;
panel_height = temp;
}
panel_rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
}
if (have_panel_props) {
ply_pixel_buffer_set_device_rotation (bgrt_buffer, panel_rotation);
ply_pixel_buffer_set_device_scale (bgrt_buffer, panel_scale);
}
width = ply_pixel_buffer_get_width (bgrt_buffer);
height = ply_pixel_buffer_get_height (bgrt_buffer);
x_offset = (screen_width - width) / 2;
y_offset = screen_height * 382 / 1000 - height / 2;
/*
* On laptops / tablets the LCD panel is typically brought up in
* its native resolution, so we can trust the x- and y-offset values
* provided by the firmware to be correct for a screen with the panels
* resolution.
*
* Moreover some laptop / tablet firmwares to do all kind of hacks wrt
* the y offset. This happens especially on devices where the panel is
* mounted 90 degrees rotated, but also on other devices.
*
* So on devices with an internal LCD panel, we prefer to use the
* firmware provided offsets, to make sure we match its quirky behavior.
*
* We check that the x-offset matches what we expect for the panel's
* native resolution to make sure that the values are indeed for the
* panel's native resolution and then we correct for any difference
* between the (external) screen's and the panel's resolution.
*/
if (have_panel_props &&
(panel_width - view->plugin->background_bgrt_raw_width) / 2 == sysfs_x_offset) {
if (panel_rotation == PLY_PIXEL_BUFFER_ROTATE_CLOCKWISE ||
panel_rotation == PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE) {
/*
* For left side up panels the y_offset is from the
* right side of the image once rotated upright (the
* top of the physicial LCD panel is on the right side).
* Our coordinates have the left side as 0, so we need
* to "flip" the y_offset in this case.
*/
if (panel_rotation == PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE)
sysfs_y_offset = panel_height - view->plugin->background_bgrt_raw_height - sysfs_y_offset;
/* 90 degrees rotated, swap x and y */
x_offset = sysfs_y_offset / panel_scale;
y_offset = sysfs_x_offset / panel_scale;
x_offset += (screen_width - panel_height / panel_scale) / 2;
y_offset += (screen_height - panel_width / panel_scale) * 382 / 1000;
} else {
/* Normal orientation */
x_offset = sysfs_x_offset / panel_scale;
y_offset = sysfs_y_offset / panel_scale;
x_offset += (screen_width - panel_width / panel_scale) / 2;
y_offset += (screen_height - panel_height / panel_scale) * 382 / 1000;
}
}
/*
* On desktops (no panel) we normally do not use the BGRT provided
* xoffset and yoffset because the resolution they are intended for
* may be differtent then the resolution of the current display.
*
* On some desktops (no panel) the image gets centered not only
* horizontally, but also vertically. In this case our default of using
* the golden ratio for the vertical position causes the BGRT image
* to jump. To avoid this we check here if the provided xoffset and
* yoffset perfectly center the image and in that case we use them.
*/
if (!have_panel_props && screen_scale == 1 &&
(screen_width - width ) / 2 == sysfs_x_offset &&
(screen_height - height) / 2 == sysfs_y_offset) {
x_offset = sysfs_x_offset;
y_offset = sysfs_y_offset;
}
ply_trace ("using %dx%d bgrt image centered at %dx%d for %dx%d screen",
width, height, x_offset, y_offset, screen_width, screen_height);
view->background_buffer = ply_pixel_buffer_new (screen_width * screen_scale, screen_height * screen_scale);
ply_pixel_buffer_set_device_scale (view->background_buffer, screen_scale);
ply_pixel_buffer_fill_with_hex_color (view->background_buffer, NULL, 0x000000);
if (x_offset >= 0 && y_offset >= 0) {
bgrt_buffer = ply_pixel_buffer_rotate_upright (bgrt_buffer);
ply_pixel_buffer_fill_with_buffer (view->background_buffer, bgrt_buffer, x_offset, y_offset);
ply_pixel_buffer_free (bgrt_buffer);
}
}
static bool
view_load (view_t *view)
{
unsigned long x, y, width, title_height = 0, subtitle_height = 0;
unsigned long screen_width, screen_height, screen_scale;
char *image_dir, *image_path;
ply_boot_splash_plugin_t *plugin;
ply_pixel_buffer_t *buffer;
plugin = view->plugin;
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
buffer = ply_renderer_get_buffer_for_head(
ply_pixel_display_get_renderer (view->display),
ply_pixel_display_get_renderer_head (view->display));
screen_scale = ply_pixel_buffer_get_device_scale (buffer);
view_set_bgrt_background (view);
if (!view->background_buffer && plugin->background_tile_image != NULL) {
ply_trace ("tiling background to %lux%lu", screen_width, screen_height);
/* Create a buffer at screen scale so that we only do the slow interpolating scale once */
view->background_buffer = ply_pixel_buffer_new (screen_width * screen_scale, screen_height * screen_scale);
ply_pixel_buffer_set_device_scale (view->background_buffer, screen_scale);
if (plugin->background_start_color != plugin->background_end_color)
ply_pixel_buffer_fill_with_gradient (view->background_buffer, NULL,
plugin->background_start_color,
plugin->background_end_color);
else
ply_pixel_buffer_fill_with_hex_color (view->background_buffer, NULL,
plugin->background_start_color);
buffer = ply_pixel_buffer_tile (ply_image_get_buffer (plugin->background_tile_image), screen_width, screen_height);
ply_pixel_buffer_fill_with_buffer (view->background_buffer, buffer, 0, 0);
ply_pixel_buffer_free (buffer);
}
if (view->watermark_image == NULL && plugin->mode_settings[plugin->mode].watermark_imagename) {
image_path = detect_image(plugin->mode_settings[plugin->mode].watermark_imagename,
screen_width, screen_height);
ply_trace ("assing watermark %s", image_path);
view->watermark_image = ply_image_new (image_path);
free (image_path);
}
if (view->watermark_image != NULL) {
ply_trace ("loading watermark image");
if (!ply_image_load (view->watermark_image)) {
ply_image_free (view->watermark_image);
view->watermark_image = NULL;
}
}
if (view->watermark_image != NULL) {
view->watermark_area.width = ply_image_get_width (view->watermark_image);
view->watermark_area.height = ply_image_get_height (view->watermark_image);
view->watermark_area.x = screen_width * plugin->watermark_horizontal_alignment - ply_image_get_width (view->watermark_image) * plugin->watermark_horizontal_alignment;
view->watermark_area.y = screen_height * plugin->watermark_vertical_alignment - ply_image_get_height (view->watermark_image) * plugin->watermark_vertical_alignment;
ply_trace ("using %ldx%ld watermark centered at %ldx%ld for %ldx%ld screen",
view->watermark_area.width, view->watermark_area.height,
view->watermark_area.x, view->watermark_area.y,
screen_width, screen_height);
}
ply_trace ("loading entry");
if (!ply_entry_load (view->entry))
return false;
ply_keymap_icon_load (view->keymap_icon);
ply_capslock_icon_load (view->capslock_icon);
view_load_end_animation (view);
if (view->progress_animation != NULL) {
ply_trace ("loading progress animation");
if (!ply_progress_animation_load (view->progress_animation)) {
ply_trace ("optional progress animation wouldn't load");
ply_progress_animation_free (view->progress_animation);
view->progress_animation = NULL;
}
} else {
ply_trace ("this theme has no progress animation");
}
if (view->throbber != NULL) {
ply_trace ("loading throbber");
if (!ply_throbber_load (view->throbber)) {
ply_trace ("optional throbber was not loaded");
ply_throbber_free (view->throbber);
view->throbber = NULL;
}
} else {
ply_trace ("this theme has no throbber\n");
}
if (plugin->mode_settings[plugin->mode].title) {
ply_label_set_text (view->title_label,
_(plugin->mode_settings[plugin->mode].title));
title_height = ply_label_get_height (view->title_label);
} else {
ply_label_hide (view->title_label);
}
if (plugin->mode_settings[plugin->mode].subtitle) {
ply_label_set_text (view->subtitle_label,
_(plugin->mode_settings[plugin->mode].subtitle));
subtitle_height = ply_label_get_height (view->subtitle_label);
} else {
ply_label_hide (view->subtitle_label);
}
y = (screen_height - title_height - 2 * subtitle_height) * plugin->title_vertical_alignment;
view->title_area.width = 0;
view->title_area.height = 0;
view->title_area.x = -1;
view->title_area.y = 0;
if (plugin->mode_settings[plugin->mode].title) {
width = ply_label_get_width (view->title_label);
x = (screen_width - width) * plugin->title_horizontal_alignment;
ply_trace ("using %ldx%ld title centered at %ldx%ld for %ldx%ld screen",
width, title_height, x, y, screen_width, screen_height);
ply_label_show (view->title_label, view->display, x, y);
view->title_area.width = width;
view->title_area.height = 2 * title_height;
view->title_area.x = x;
view->title_area.y = y;
/* Use subtitle_height pixels seperation between title and subtitle */
y += title_height + subtitle_height;
}
if (plugin->mode_settings[plugin->mode].subtitle) {
width = ply_label_get_width (view->subtitle_label);
x = (screen_width - width) * plugin->title_horizontal_alignment;
ply_trace ("using %ldx%ld subtitle centered at %ldx%ld for %ldx%ld screen",
width, subtitle_height, x, y, screen_width, screen_height);
ply_label_show (view->subtitle_label, view->display, x, y);
if( x == -1 || x < view->title_area.x ) {
view->title_area.x = x;
}
if( width > view->title_area.width ) {
view->title_area.width = width;
}
if (view->title_area.height != 0) {
view->title_area.height = title_height + subtitle_height + subtitle_height * 2;
} else {
view->title_area.height = subtitle_height * 2;
}
}
uint32_t title_color = 0xff00ff;
title_color = plugin->mode_settings[plugin->mode].title_color;
if(view->title_label) {
ply_label_set_color(view->title_label,
((title_color >> 16) & 0xff) / 255.0f,
((title_color >> 8) & 0xff) / 255.0f,
(title_color & 0xff) / 255.0f,
1.0f);
ply_label_set_color(view->subtitle_label,
((title_color >> 16) & 0xff) / 255.0f,
((title_color >> 8) & 0xff) / 255.0f,
(title_color & 0xff) / 255.0f,
1.0f);
}
return true;
}
static bool
load_views (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
bool view_loaded;
view_t *view;
view_loaded = false;
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
if (view_load (view))
view_loaded = true;
node = ply_list_get_next_node (plugin->views, node);
}
return view_loaded;
}
static void
view_redraw (view_t *view)
{
unsigned long screen_width, screen_height;
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
ply_pixel_display_draw_area (view->display, 0, 0,
screen_width, screen_height);
}
static void
redraw_views (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
view_redraw (view);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
pause_views (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
ply_trace ("pausing views");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
ply_pixel_display_pause_updates (view->display);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
unpause_views (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
ply_trace ("unpausing views");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
ply_pixel_display_unpause_updates (view->display);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
view_start_end_animation (view_t *view,
ply_trigger_t *trigger)
{
ply_boot_splash_plugin_t *plugin = view->plugin;
unsigned long screen_width, screen_height;
long x, y, width, height;
ply_progress_bar_hide (view->progress_bar);
if (view->progress_animation != NULL)
ply_progress_animation_hide (view->progress_animation);
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
width = ply_animation_get_width (view->end_animation);
height = ply_animation_get_height (view->end_animation);
x = plugin->mode_settings[plugin->mode].animation_horizontal_alignment * screen_width - width / 2.0;
y = calculate_animation_y(view,
plugin->mode_settings[plugin->mode].animation_vertical_alignment,
plugin->mode_settings[plugin->mode].animation_vertical_alignment_type,
screen_height, height);
ply_trace ("starting end sequence animation for %ldx%ld view", width, height);
ply_animation_start (view->end_animation,
view->display,
trigger, x, y);
view->animation_bottom = y + height;
}
static void
on_view_throbber_stopped (view_t *view)
{
view_start_end_animation (view, view->end_trigger);
view->end_trigger = NULL;
}
static void
view_start_progress_animation (view_t *view)
{
ply_boot_splash_plugin_t *plugin;
long x, y;
long width, height;
unsigned long screen_width, screen_height;
assert (view != NULL);
plugin = view->plugin;
plugin->is_idle = false;
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
ply_pixel_display_draw_area (view->display, 0, 0,
screen_width, screen_height);
if (plugin->mode_settings[plugin->mode].use_progress_bar) {
if (plugin->progress_bar_width != -1)
width = plugin->progress_bar_width;
else
width = screen_width;
height = plugin->progress_bar_height;
x = plugin->progress_bar_horizontal_alignment * (screen_width - width);
//y = plugin->progress_bar_vertical_alignment * (screen_height - height);
y = calculate_animation_y(view,
plugin->progress_bar_vertical_alignment,
plugin->mode_settings[plugin->mode].animation_vertical_alignment_type,
screen_height, height);
ply_progress_bar_show (view->progress_bar, view->display,
x, y, width, height);
ply_pixel_display_draw_area (view->display, x, y, width, height);
view->animation_bottom = y + height;
}
if (plugin->mode_settings[plugin->mode].use_animation &&
view->throbber != NULL) {
width = ply_throbber_get_width (view->throbber);
height = ply_throbber_get_height (view->throbber);
x = plugin->mode_settings[plugin->mode].animation_horizontal_alignment * screen_width - width / 2.0;
y = calculate_animation_y(view,
plugin->mode_settings[plugin->mode].animation_vertical_alignment,
plugin->mode_settings[plugin->mode].animation_vertical_alignment_type,
screen_height, height);
ply_throbber_start (view->throbber,
plugin->loop,
view->display, x, y);
ply_pixel_display_draw_area (view->display, x, y, width, height);
view->animation_bottom = y + height;
}
/* We don't really know how long shutdown will so
* don't show the progress animation
*/
if (plugin->mode == PLY_BOOT_SPLASH_MODE_SHUTDOWN ||
plugin->mode == PLY_BOOT_SPLASH_MODE_REBOOT)
return;
if (plugin->mode_settings[plugin->mode].use_animation &&
view->progress_animation != NULL) {
width = ply_progress_animation_get_width (view->progress_animation);
height = ply_progress_animation_get_height (view->progress_animation);
x = plugin->mode_settings[plugin->mode].animation_horizontal_alignment * screen_width - width / 2.0;
y = calculate_animation_y(view,
plugin->mode_settings[plugin->mode].animation_vertical_alignment,
plugin->mode_settings[plugin->mode].animation_vertical_alignment_type,
screen_height, height);
ply_progress_animation_show (view->progress_animation,
view->display, x, y);
ply_pixel_display_draw_area (view->display, x, y, width, height);
view->animation_bottom = y + height;
}
}
static void
view_show_prompt (view_t *view,
const char *prompt,
const char *entry_text,
int number_of_bullets)
{
ply_boot_splash_plugin_t *plugin;
unsigned long screen_width, screen_height, entry_width, entry_height;
unsigned long keyboard_indicator_width, keyboard_indicator_height;
bool show_keyboard_indicators = false;
long dialog_bottom;
int x, y;
assert (view != NULL);
plugin = view->plugin;
screen_width = ply_pixel_display_get_width (view->display);
screen_height = ply_pixel_display_get_height (view->display);
if (ply_entry_is_hidden (view->entry)) {
view->lock_area.width = ply_image_get_width (plugin->lock_image);
view->lock_area.height = ply_image_get_height (plugin->lock_image);
entry_width = ply_entry_get_width (view->entry);
entry_height = ply_entry_get_height (view->entry);
if (plugin->box_image) {
view->box_area.width = ply_image_get_width (plugin->box_image);
view->box_area.height = ply_image_get_height (plugin->box_image);
view->box_area.x = (screen_width - view->box_area.width) * plugin->dialog_horizontal_alignment;
view->box_area.y = (screen_height - view->box_area.height) * plugin->dialog_vertical_alignment;
view->dialog_area = view->box_area;
} else {
view->dialog_area.width = view->lock_area.width + entry_width;
view->dialog_area.height = MAX(view->lock_area.height, entry_height);
view->dialog_area.x = (screen_width - view->dialog_area.width) * plugin->dialog_horizontal_alignment;
view->dialog_area.y = (screen_height - view->dialog_area.height) * plugin->dialog_vertical_alignment;
}
view->lock_area.x =
view->dialog_area.x +
(view->dialog_area.width -
(view->lock_area.width + entry_width)) / 2.0;
view->lock_area.y =
view->dialog_area.y +
(view->dialog_area.height - view->lock_area.height) / 2.0;
x = view->lock_area.x + view->lock_area.width;
y = view->dialog_area.y +
(view->dialog_area.height - entry_height) / 2.0;
ply_entry_show (view->entry, plugin->loop, view->display, x, y);
show_keyboard_indicators = true;
}
if (entry_text != NULL)
ply_entry_set_text (view->entry, entry_text);
if (number_of_bullets != -1)
ply_entry_set_bullet_count (view->entry, number_of_bullets);
dialog_bottom = view->dialog_area.y + view->dialog_area.height;
if (prompt != NULL) {
ply_label_set_text (view->label, prompt);
/* We center the prompt in the middle and use 80% of the horizontal space */
int label_width = screen_width * 100 / 80;
ply_label_set_alignment (view->label, PLY_LABEL_ALIGN_CENTER);
ply_label_set_width (view->label, label_width);
x = (screen_width - label_width) / 2;
y = dialog_bottom;
ply_label_show (view->label, view->display, x, y);
dialog_bottom += ply_label_get_height (view->label);
}
if (show_keyboard_indicators) {
keyboard_indicator_width =
ply_keymap_icon_get_width (view->keymap_icon);
keyboard_indicator_height = MAX(
ply_capslock_icon_get_height (view->capslock_icon),
ply_keymap_icon_get_height (view->keymap_icon));
x = (screen_width - keyboard_indicator_width) * plugin->dialog_horizontal_alignment;
y = dialog_bottom + keyboard_indicator_height / 2 +
(keyboard_indicator_height - ply_keymap_icon_get_height (view->keymap_icon)) / 2.0;
ply_keymap_icon_show (view->keymap_icon, x, y);
x += ply_keymap_icon_get_width (view->keymap_icon);
y = dialog_bottom + keyboard_indicator_height / 2 +
(keyboard_indicator_height - ply_capslock_icon_get_height (view->capslock_icon)) / 2.0;
ply_capslock_icon_show (view->capslock_icon, plugin->loop, view->display, x, y);
}
}
static void
view_hide_prompt (view_t *view)
{
assert (view != NULL);
ply_entry_hide (view->entry);
ply_capslock_icon_hide (view->capslock_icon);
ply_keymap_icon_hide (view->keymap_icon);
ply_label_hide (view->label);
}
static void
load_mode_settings (ply_boot_splash_plugin_t *plugin,
ply_key_file_t *key_file,
const char *group_name,
ply_boot_splash_mode_t mode)
{
mode_settings_t *settings = &plugin->mode_settings[mode];
settings->suppress_messages =
ply_key_file_get_bool (key_file, group_name, "SuppressMessages");
settings->progress_bar_show_percent_complete =
ply_key_file_get_bool (key_file, group_name, "ProgressBarShowPercentComplete");
settings->use_progress_bar =
ply_key_file_get_bool (key_file, group_name, "UseProgressBar");
settings->use_firmware_background =
ply_key_file_get_bool (key_file, group_name, "UseFirmwareBackground");
/* This defaults to !use_progress_bar for compat. with older themes */
if (ply_key_file_has_key (key_file, group_name, "UseAnimation"))
settings->use_animation =
ply_key_file_get_bool (key_file, group_name, "UseAnimation");
else
settings->use_animation = !settings->use_progress_bar;
/* This defaults to true for compat. with older themes */
if (ply_key_file_has_key (key_file, group_name, "UseEndAnimation"))
settings->use_end_animation =
ply_key_file_get_bool (key_file, group_name, "UseEndAnimation");
else
settings->use_end_animation = true;
/* If any mode uses the firmware background, then we need to load it */
if (settings->use_firmware_background)
plugin->use_firmware_background = true;
settings->watermark_imagename = ply_key_file_get_value (key_file, group_name, "WatermarkImage");
if( settings->watermark_imagename == NULL &&
plugin->watermark_imagename != NULL )
settings->watermark_imagename = strdup(plugin->watermark_imagename);
settings->title = ply_key_file_get_value (key_file, group_name, "Title");
settings->subtitle = ply_key_file_get_value (key_file, group_name, "SubTitle");
settings->title_color = ply_key_file_get_long (key_file, group_name, "TitleColor",
plugin->title_color);
settings->background_start_color =
ply_key_file_get_long (key_file, group_name,
"BackgroundStartColor",
plugin->background_start_color);
settings->background_end_color =
ply_key_file_get_long (key_file, group_name,
"BackgroundEndColor",
plugin->background_end_color);
/* Throbber, progress- and end-animation alignment */
settings->animation_horizontal_alignment =
ply_key_file_get_double (key_file, group_name,
"HorizontalAlignment", plugin->animation_horizontal_alignment);
settings->animation_vertical_alignment =
ply_key_file_get_double (key_file, group_name,
"VerticalAlignment", plugin->animation_vertical_alignment);
settings->animation_vertical_alignment_type =
ply_key_file_get_value (key_file, group_name,
"VerticalAlignmentType");
if( settings->animation_vertical_alignment_type == NULL &&
plugin->animation_vertical_alignment_type != NULL )
settings->animation_vertical_alignment_type = strdup(plugin->animation_vertical_alignment_type);
}
static ply_boot_splash_plugin_t *
create_plugin (ply_key_file_t *key_file)
{
ply_boot_splash_plugin_t *plugin;
char *image_dir, *image_path;
char *transition;
char *progress_function;
srand ((int) ply_get_timestamp ());
plugin = calloc (1, sizeof(ply_boot_splash_plugin_t));
image_dir = ply_key_file_get_value (key_file, "calculate", "ImageDir");
ply_trace ("Using '%s' as working directory", image_dir);
asprintf (&image_path, "%s/lock.png", image_dir);
plugin->lock_image = ply_image_new (image_path);
free (image_path);
asprintf (&image_path, "%s/box.png", image_dir);
plugin->box_image = ply_image_new (image_path);
free (image_path);
asprintf (&image_path, "%s/corner-image.png", image_dir);
plugin->corner_image = ply_image_new (image_path);
free (image_path);
asprintf (&image_path, "%s/header-image.png", image_dir);
plugin->header_image = ply_image_new (image_path);
free (image_path);
asprintf (&image_path, "%s/background-tile.png", image_dir);
plugin->background_tile_image = ply_image_new (image_path);
free (image_path);
plugin->animation_dir = image_dir;
plugin->font = ply_key_file_get_value (key_file, "calculate", "Font");
plugin->title_font = ply_key_file_get_value (key_file, "calculate", "TitleFont");
/* Throbber, progress- and end-animation alignment */
plugin->animation_horizontal_alignment =
ply_key_file_get_double (key_file, "calculate",
"HorizontalAlignment", 0.5);
plugin->animation_vertical_alignment =
ply_key_file_get_double (key_file, "calculate",
"VerticalAlignment", 0.5);
plugin->animation_vertical_alignment_type =
ply_key_file_get_value (key_file, "calculate",
"VerticalAlignmentType");
/* Progressbar alignment, this defaults to the animation alignment
* for compatibility with older themes.
*/
plugin->progress_bar_horizontal_alignment =
ply_key_file_get_double (key_file, "calculate",
"ProgressBarHorizontalAlignment",
plugin->animation_horizontal_alignment);
plugin->progress_bar_vertical_alignment =
ply_key_file_get_double (key_file, "calculate",
"ProgressBarVerticalAlignment",
plugin->animation_vertical_alignment);
/* Watermark alignment */
plugin->watermark_horizontal_alignment =
ply_key_file_get_double (key_file, "calculate",
"WatermarkHorizontalAlignment", 1.0);
plugin->watermark_vertical_alignment =
ply_key_file_get_double (key_file, "calculate",
"WatermarkVerticalAlignment", 0.5);
/* Password (or other) dialog alignment */
plugin->dialog_horizontal_alignment =
ply_key_file_get_double (key_file, "calculate",
"DialogHorizontalAlignment", 0.5);
plugin->dialog_vertical_alignment =
ply_key_file_get_double (key_file, "calculate",
"DialogVerticalAlignment", 0.5);
/* Title alignment */
plugin->title_horizontal_alignment =
ply_key_file_get_double (key_file, "calculate",
"TitleHorizontalAlignment", 0.5);
plugin->title_vertical_alignment =
ply_key_file_get_double (key_file, "calculate",
"TitleVerticalAlignment", 0.5);
plugin->transition = PLY_PROGRESS_ANIMATION_TRANSITION_NONE;
transition = ply_key_file_get_value (key_file, "calculate", "Transition");
if (transition != NULL) {
if (strcmp (transition, "fade-over") == 0)
plugin->transition = PLY_PROGRESS_ANIMATION_TRANSITION_FADE_OVER;
else if (strcmp (transition, "cross-fade") == 0)
plugin->transition = PLY_PROGRESS_ANIMATION_TRANSITION_CROSS_FADE;
else if (strcmp (transition, "merge-fade") == 0)
plugin->transition = PLY_PROGRESS_ANIMATION_TRANSITION_MERGE_FADE;
}
free (transition);
plugin->transition_duration =
ply_key_file_get_double (key_file, "calculate",
"TransitionDuration", 0.0);
plugin->background_start_color =
ply_key_file_get_long (key_file, "calculate",
"BackgroundStartColor",
PLYMOUTH_BACKGROUND_START_COLOR);
plugin->background_end_color =
ply_key_file_get_long (key_file, "calculate",
"BackgroundEndColor",
PLYMOUTH_BACKGROUND_END_COLOR);
plugin->title_color = ply_key_file_get_long (
key_file, "calculate", "TitleColor", 0xffffff);
plugin->progress_bar_bg_color =
ply_key_file_get_long (key_file, "calculate",
"ProgressBarBackgroundColor",
0xffffff /* white */);
plugin->progress_bar_fg_color =
ply_key_file_get_long (key_file, "calculate",
"ProgressBarForegroundColor",
0x000000 /* black */);
plugin->progress_bar_width =
ply_key_file_get_long (key_file, "calculate",
"ProgressBarWidth",
PROGRESS_BAR_WIDTH);
plugin->progress_bar_height =
ply_key_file_get_long (key_file, "calculate",
"ProgressBarHeight",
PROGRESS_BAR_HEIGHT);
plugin->watermark_imagename = ply_key_file_get_value (key_file, "calculate", "WatermarkImage");
load_mode_settings (plugin, key_file, "boot-up", PLY_BOOT_SPLASH_MODE_BOOT_UP);
load_mode_settings (plugin, key_file, "shutdown", PLY_BOOT_SPLASH_MODE_SHUTDOWN);
load_mode_settings (plugin, key_file, "reboot", PLY_BOOT_SPLASH_MODE_REBOOT);
load_mode_settings (plugin, key_file, "updates", PLY_BOOT_SPLASH_MODE_UPDATES);
load_mode_settings (plugin, key_file, "system-upgrade", PLY_BOOT_SPLASH_MODE_SYSTEM_UPGRADE);
load_mode_settings (plugin, key_file, "firmware-upgrade", PLY_BOOT_SPLASH_MODE_FIRMWARE_UPGRADE);
if (plugin->use_firmware_background)
plugin->background_bgrt_image = ply_image_new ("/sys/firmware/acpi/bgrt/image");
plugin->dialog_clears_firmware_background =
ply_key_file_get_bool (key_file, "calculate", "DialogClearsFirmwareBackground");
plugin->message_below_animation =
ply_key_file_get_bool (key_file, "calculate", "MessageBelowAnimation");
progress_function = ply_key_file_get_value (key_file, "calculate", "ProgressFunction");
if (progress_function != NULL) {
if (strcmp (progress_function, "wwoods") == 0) {
ply_trace ("Using wwoods progress function");
plugin->progress_function = PROGRESS_FUNCTION_TYPE_WWOODS;
} else if (strcmp (progress_function, "linear") == 0) {
ply_trace ("Using linear progress function");
plugin->progress_function = PROGRESS_FUNCTION_TYPE_LINEAR;
} else {
ply_trace ("unknown progress function %s, defaulting to linear", progress_function);
plugin->progress_function = PROGRESS_FUNCTION_TYPE_LINEAR;
}
free (progress_function);
}
plugin->views = ply_list_new ();
return plugin;
}
static void
free_views (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
ply_trace ("freeing views");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
ply_list_node_t *next_node;
view_t *view;
view = ply_list_node_get_data (node);
next_node = ply_list_get_next_node (plugin->views, node);
view_free (view);
ply_list_remove_node (plugin->views, node);
node = next_node;
}
ply_list_free (plugin->views);
plugin->views = NULL;
}
static void
destroy_plugin (ply_boot_splash_plugin_t *plugin)
{
int i;
if (plugin == NULL)
return;
ply_trace ("destroying plugin");
if (plugin->loop != NULL) {
stop_animation (plugin);
ply_event_loop_stop_watching_for_exit (plugin->loop, (ply_event_loop_exit_handler_t)
detach_from_event_loop,
plugin);
detach_from_event_loop (plugin);
}
ply_image_free (plugin->lock_image);
if (plugin->box_image != NULL)
ply_image_free (plugin->box_image);
if (plugin->corner_image != NULL)
ply_image_free (plugin->corner_image);
if (plugin->header_image != NULL)
ply_image_free (plugin->header_image);
if (plugin->background_tile_image != NULL)
ply_image_free (plugin->background_tile_image);
if (plugin->background_bgrt_image != NULL)
ply_image_free (plugin->background_bgrt_image);
for (i = 0; i < PLY_BOOT_SPLASH_MODE_COUNT; i++) {
free (plugin->mode_settings[i].title);
free (plugin->mode_settings[i].watermark_imagename);
free (plugin->mode_settings[i].subtitle);
free (plugin->mode_settings[i].animation_vertical_alignment_type);
}
free (plugin->font);
free (plugin->title_font);
free (plugin->animation_vertical_alignment_type);
free (plugin->animation_dir);
free (plugin->watermark_imagename);
free_views (plugin);
free (plugin);
}
static void
start_end_animation (ply_boot_splash_plugin_t *plugin,
ply_trigger_t *trigger)
{
ply_list_node_t *node;
view_t *view;
if (!plugin->mode_settings[plugin->mode].use_animation) {
ply_trigger_pull (trigger, NULL);
return;
}
if (!plugin->mode_settings[plugin->mode].use_end_animation) {
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
ply_progress_bar_hide (view->progress_bar);
if (view->throbber != NULL)
ply_throbber_stop (view->throbber, NULL);
if (view->progress_animation != NULL)
ply_progress_animation_hide (view->progress_animation);
node = ply_list_get_next_node (plugin->views, node);
}
ply_trigger_pull (trigger, NULL);
return;
}
ply_trace ("starting end animation");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
ply_trigger_ignore_next_pull (trigger);
if (view->throbber != NULL) {
ply_trigger_t *throbber_trigger;
ply_trace ("stopping throbber");
view->end_trigger = trigger;
throbber_trigger = ply_trigger_new (NULL);
ply_trigger_add_handler (throbber_trigger,
(ply_trigger_handler_t)
on_view_throbber_stopped,
view);
ply_throbber_stop (view->throbber, throbber_trigger);
} else {
view_start_end_animation (view, trigger);
}
node = ply_list_get_next_node (plugin->views, node);
}
ply_trigger_pull (trigger, NULL);
}
static void
start_progress_animation (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
if (plugin->is_animating)
return;
ply_trace ("starting animation");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
view_start_progress_animation (view);
node = ply_list_get_next_node (plugin->views, node);
}
plugin->is_animating = true;
/* We don't really know how long shutdown will, take
* but it's normally really fast, so just jump to
* the end animation
*/
if (plugin->mode_settings[plugin->mode].use_end_animation &&
(plugin->mode == PLY_BOOT_SPLASH_MODE_SHUTDOWN ||
plugin->mode == PLY_BOOT_SPLASH_MODE_REBOOT))
become_idle (plugin, NULL);
}
static void
stop_animation (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
assert (plugin != NULL);
assert (plugin->loop != NULL);
if (!plugin->is_animating)
return;
ply_trace ("stopping animation");
plugin->is_animating = false;
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
ply_progress_bar_hide (view->progress_bar);
if (view->progress_animation != NULL)
ply_progress_animation_hide (view->progress_animation);
if (view->throbber != NULL)
ply_throbber_stop (view->throbber, NULL);
if (view->end_animation != NULL)
ply_animation_stop (view->end_animation);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
detach_from_event_loop (ply_boot_splash_plugin_t *plugin)
{
plugin->loop = NULL;
}
static void
draw_background (view_t *view,
ply_pixel_buffer_t *pixel_buffer,
int x,
int y,
int width,
int height)
{
ply_boot_splash_plugin_t *plugin;
ply_rectangle_t area;
bool use_black_background = false;
plugin = view->plugin;
area.x = x;
area.y = y;
area.width = width;
area.height = height;
/* When using the firmware logo as background and we should not use
* it for this mode, use solid black as background.
*/
if (plugin->background_bgrt_image &&
!plugin->mode_settings[plugin->mode].use_firmware_background)
use_black_background = true;
/* When using the firmware logo as background, use solid black as
* background for dialogs.
*/
if ((plugin->state == PLY_BOOT_SPLASH_DISPLAY_QUESTION_ENTRY ||
plugin->state == PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY) &&
plugin->background_bgrt_image && plugin->dialog_clears_firmware_background)
use_black_background = true;
if (use_black_background)
ply_pixel_buffer_fill_with_hex_color (pixel_buffer, &area, 0);
else if (view->background_buffer != NULL)
ply_pixel_buffer_fill_with_buffer (pixel_buffer, view->background_buffer, 0, 0);
else if (plugin->mode_settings[plugin->mode].background_start_color != plugin->mode_settings[plugin->mode].background_end_color)
ply_pixel_buffer_fill_with_gradient (pixel_buffer, &area,
plugin->mode_settings[plugin->mode].background_start_color,
plugin->mode_settings[plugin->mode].background_end_color);
else
ply_pixel_buffer_fill_with_hex_color (pixel_buffer, &area,
plugin->mode_settings[plugin->mode].background_start_color);
if (view->watermark_image != NULL) {
uint32_t *data;
data = ply_image_get_data (view->watermark_image);
ply_pixel_buffer_fill_with_argb32_data (pixel_buffer, &view->watermark_area, data);
}
}
static void
on_draw (view_t *view,
ply_pixel_buffer_t *pixel_buffer,
int x,
int y,
int width,
int height)
{
ply_boot_splash_plugin_t *plugin;
ply_rectangle_t screen_area;
ply_rectangle_t image_area;
plugin = view->plugin;
draw_background (view, pixel_buffer, x, y, width, height);
ply_pixel_buffer_get_size (pixel_buffer, &screen_area);
if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_QUESTION_ENTRY ||
plugin->state == PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY) {
uint32_t *box_data, *lock_data;
if (plugin->box_image) {
box_data = ply_image_get_data (plugin->box_image);
ply_pixel_buffer_fill_with_argb32_data (pixel_buffer,
&view->box_area,
box_data);
}
ply_entry_draw_area (view->entry,
pixel_buffer,
x, y, width, height);
ply_keymap_icon_draw_area (view->keymap_icon,
pixel_buffer,
x, y, width, height);
ply_capslock_icon_draw_area (view->capslock_icon,
pixel_buffer,
x, y, width, height);
ply_label_draw_area (view->label,
pixel_buffer,
x, y, width, height);
lock_data = ply_image_get_data (plugin->lock_image);
ply_pixel_buffer_fill_with_argb32_data (pixel_buffer,
&view->lock_area,
lock_data);
} else {
if (plugin->mode_settings[plugin->mode].use_progress_bar)
ply_progress_bar_draw_area (view->progress_bar, pixel_buffer,
x, y, width, height);
if (plugin->mode_settings[plugin->mode].use_animation &&
view->throbber != NULL)
ply_throbber_draw_area (view->throbber, pixel_buffer,
x, y, width, height);
if (plugin->mode_settings[plugin->mode].use_animation &&
view->progress_animation != NULL)
ply_progress_animation_draw_area (view->progress_animation,
pixel_buffer,
x, y, width, height);
if (plugin->mode_settings[plugin->mode].use_animation &&
view->end_animation != NULL)
ply_animation_draw_area (view->end_animation,
pixel_buffer,
x, y, width, height);
if (plugin->corner_image != NULL) {
image_area.width = ply_image_get_width (plugin->corner_image);
image_area.height = ply_image_get_height (plugin->corner_image);
image_area.x = screen_area.width - image_area.width - 20;
image_area.y = screen_area.height - image_area.height - 20;
ply_pixel_buffer_fill_with_argb32_data (pixel_buffer, &image_area, ply_image_get_data (plugin->corner_image));
}
if (plugin->header_image != NULL) {
long sprite_height;
if (view->progress_animation != NULL)
sprite_height = ply_progress_animation_get_height (view->progress_animation);
else
sprite_height = 0;
if (view->throbber != NULL)
sprite_height = MAX (ply_throbber_get_height (view->throbber),
sprite_height);
image_area.width = ply_image_get_width (plugin->header_image);
image_area.height = ply_image_get_height (plugin->header_image);
image_area.x = screen_area.width / 2.0 - image_area.width / 2.0;
image_area.y = plugin->mode_settings[plugin->mode].animation_vertical_alignment * screen_area.height - sprite_height / 2.0 - image_area.height;
ply_pixel_buffer_fill_with_argb32_data (pixel_buffer, &image_area, ply_image_get_data (plugin->header_image));
}
ply_label_draw_area (view->title_label,
pixel_buffer,
x, y, width, height);
ply_label_draw_area (view->subtitle_label,
pixel_buffer,
x, y, width, height);
}
ply_label_draw_area (view->message_label,
pixel_buffer,
x, y, width, height);
}
static void
add_pixel_display (ply_boot_splash_plugin_t *plugin,
ply_pixel_display_t *display)
{
view_t *view;
ply_trace ("adding pixel display to plugin");
view = view_new (plugin, display);
ply_pixel_display_set_draw_handler (view->display,
(ply_pixel_display_draw_handler_t)
on_draw, view);
if (plugin->is_visible) {
if (view_load (view)) {
ply_list_append_data (plugin->views, view);
if (plugin->is_animating)
view_start_progress_animation (view);
} else {
view_free (view);
}
} else {
ply_list_append_data (plugin->views, view);
}
}
static void
remove_pixel_display (ply_boot_splash_plugin_t *plugin,
ply_pixel_display_t *display)
{
ply_list_node_t *node;
ply_trace ("removing pixel display from plugin");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view_t *view;
ply_list_node_t *next_node;
view = ply_list_node_get_data (node);
next_node = ply_list_get_next_node (plugin->views, node);
if (view->display == display) {
ply_pixel_display_set_draw_handler (view->display, NULL, NULL);
view_free (view);
ply_list_remove_node (plugin->views, node);
return;
}
node = next_node;
}
}
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)
{
int i;
assert (plugin != NULL);
plugin->loop = loop;
plugin->mode = mode;
ply_trace ("loading lock image");
if (!ply_image_load (plugin->lock_image))
return false;
if (plugin->box_image != NULL) {
ply_trace ("loading box image");
if (!ply_image_load (plugin->box_image)) {
ply_image_free (plugin->box_image);
plugin->box_image = NULL;
}
}
if (plugin->corner_image != NULL) {
ply_trace ("loading corner image");
if (!ply_image_load (plugin->corner_image)) {
ply_image_free (plugin->corner_image);
plugin->corner_image = NULL;
}
}
if (plugin->header_image != NULL) {
ply_trace ("loading header image");
if (!ply_image_load (plugin->header_image)) {
ply_image_free (plugin->header_image);
plugin->header_image = NULL;
}
}
if (plugin->background_tile_image != NULL) {
ply_trace ("loading background tile image");
if (!ply_image_load (plugin->background_tile_image)) {
ply_image_free (plugin->background_tile_image);
plugin->background_tile_image = NULL;
}
}
if (plugin->background_bgrt_image != NULL) {
ply_trace ("loading background bgrt image");
if (ply_image_load (plugin->background_bgrt_image)) {
plugin->background_bgrt_raw_width = ply_image_get_width (plugin->background_bgrt_image);
plugin->background_bgrt_raw_height = ply_image_get_height (plugin->background_bgrt_image);
} else {
ply_image_free (plugin->background_bgrt_image);
plugin->background_bgrt_image = NULL;
for (i = 0; i < PLY_BOOT_SPLASH_MODE_COUNT; i++)
plugin->mode_settings[i].use_firmware_background = false;
plugin->use_firmware_background = false;
}
}
if (!load_views (plugin)) {
ply_trace ("couldn't load views");
return false;
}
ply_event_loop_watch_for_exit (loop, (ply_event_loop_exit_handler_t)
detach_from_event_loop,
plugin);
ply_trace ("starting boot animations");
start_progress_animation (plugin);
plugin->is_visible = true;
return true;
}
static void
update_status (ply_boot_splash_plugin_t *plugin,
const char *status)
{
assert (plugin != NULL);
}
static void
on_animation_stopped (ply_boot_splash_plugin_t *plugin)
{
if (plugin->idle_trigger != NULL) {
ply_trigger_pull (plugin->idle_trigger, NULL);
plugin->idle_trigger = NULL;
}
plugin->is_idle = true;
}
static void
update_progress_animation (ply_boot_splash_plugin_t *plugin,
double fraction_done)
{
ply_list_node_t *node;
view_t *view;
char buf[64];
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
if (view->progress_animation != NULL)
ply_progress_animation_set_fraction_done (view->progress_animation,
fraction_done);
ply_progress_bar_set_fraction_done (view->progress_bar, fraction_done);
if (!ply_progress_bar_is_hidden (view->progress_bar) &&
plugin->mode_settings[plugin->mode].progress_bar_show_percent_complete) {
snprintf (buf, sizeof(buf), _("%d%% complete"), (int)(fraction_done * 100));
view_show_message (view, buf);
}
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
on_boot_progress (ply_boot_splash_plugin_t *plugin,
double duration,
double fraction_done)
{
if (plugin->mode == PLY_BOOT_SPLASH_MODE_UPDATES ||
plugin->mode == PLY_BOOT_SPLASH_MODE_SYSTEM_UPGRADE ||
plugin->mode == PLY_BOOT_SPLASH_MODE_FIRMWARE_UPGRADE)
return;
if (plugin->state != PLY_BOOT_SPLASH_DISPLAY_NORMAL)
return;
if (plugin->is_idle)
return;
/*
* If we do not have an end animation, we keep showing progress until
* become_idle gets called.
*/
if (plugin->mode_settings[plugin->mode].use_end_animation &&
fraction_done >= SHOW_ANIMATION_FRACTION) {
if (plugin->stop_trigger == NULL) {
ply_trace ("boot progressed to end");
plugin->stop_trigger = ply_trigger_new (&plugin->stop_trigger);
ply_trigger_add_handler (plugin->stop_trigger,
(ply_trigger_handler_t)
on_animation_stopped,
plugin);
start_end_animation (plugin, plugin->stop_trigger);
}
} else {
double total_duration;
fraction_done *= (1 / SHOW_ANIMATION_FRACTION);
switch (plugin->progress_function) {
/* Fun made-up smoothing function to make the growth asymptotic:
* fraction(time,estimate)=1-2^(-(time^1.45)/estimate) */
case PROGRESS_FUNCTION_TYPE_WWOODS:
total_duration = duration / fraction_done;
fraction_done = 1.0 - pow (2.0, -pow (duration, 1.45) / total_duration) * (1.0 - fraction_done);
break;
case PROGRESS_FUNCTION_TYPE_LINEAR:
break;
}
update_progress_animation (plugin, fraction_done);
}
}
static void
hide_splash_screen (ply_boot_splash_plugin_t *plugin,
ply_event_loop_t *loop)
{
assert (plugin != NULL);
ply_trace ("hiding splash");
if (plugin->loop != NULL) {
stop_animation (plugin);
ply_event_loop_stop_watching_for_exit (plugin->loop, (ply_event_loop_exit_handler_t)
detach_from_event_loop,
plugin);
detach_from_event_loop (plugin);
}
plugin->is_visible = false;
}
static void
show_prompt (ply_boot_splash_plugin_t *plugin,
const char *prompt,
const char *entry_text,
int number_of_bullets)
{
ply_list_node_t *node;
view_t *view;
ply_trace ("showing prompt");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
view_show_prompt (view, prompt, entry_text, number_of_bullets);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
on_root_mounted (ply_boot_splash_plugin_t *plugin)
{
ply_trace ("root filesystem mounted");
plugin->root_is_mounted = true;
}
static void
become_idle (ply_boot_splash_plugin_t *plugin,
ply_trigger_t *idle_trigger)
{
ply_trace ("deactivation requested");
if (plugin->is_idle) {
ply_trace ("plugin is already idle");
ply_trigger_pull (idle_trigger, NULL);
return;
}
plugin->idle_trigger = idle_trigger;
if (plugin->stop_trigger == NULL) {
ply_trace ("waiting for plugin to stop");
plugin->stop_trigger = ply_trigger_new (&plugin->stop_trigger);
ply_trigger_add_handler (plugin->stop_trigger,
(ply_trigger_handler_t)
on_animation_stopped,
plugin);
start_end_animation (plugin, plugin->stop_trigger);
} else {
ply_trace ("already waiting for plugin to stop");
}
}
static void
hide_prompt (ply_boot_splash_plugin_t *plugin)
{
ply_list_node_t *node;
view_t *view;
ply_trace ("hiding prompt");
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
view_hide_prompt (view);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
view_show_message (view_t *view,
const char *message)
{
ply_boot_splash_plugin_t *plugin = view->plugin;
int x, y, width, height;
if (plugin->message_below_animation)
ply_label_set_alignment (view->message_label, PLY_LABEL_ALIGN_CENTER);
ply_label_set_text (view->message_label, message);
width = ply_label_get_width (view->message_label);
height = ply_label_get_height (view->message_label);
if (plugin->message_below_animation) {
x = (ply_pixel_display_get_width (view->display) - width) * 0.5;
y = view->animation_bottom + 10;
} else {
x = 10;
y = 10;
}
ply_label_show (view->message_label, view->display, x, y);
ply_pixel_display_draw_area (view->display, x, y, width, height);
}
static void
show_message (ply_boot_splash_plugin_t *plugin,
const char *message)
{
ply_list_node_t *node;
view_t *view;
if (plugin->mode_settings[plugin->mode].suppress_messages) {
ply_trace ("Suppressing message '%s'", message);
return;
}
ply_trace ("Showing message '%s'", message);
node = ply_list_get_first_node (plugin->views);
while (node != NULL) {
view = ply_list_node_get_data (node);
view_show_message (view, message);
node = ply_list_get_next_node (plugin->views, node);
}
}
static void
system_update (ply_boot_splash_plugin_t *plugin,
int progress)
{
if (plugin->mode != PLY_BOOT_SPLASH_MODE_UPDATES &&
plugin->mode != PLY_BOOT_SPLASH_MODE_SYSTEM_UPGRADE &&
plugin->mode != PLY_BOOT_SPLASH_MODE_FIRMWARE_UPGRADE)
return;
update_progress_animation (plugin, progress / 100.0);
}
static void
display_normal (ply_boot_splash_plugin_t *plugin)
{
pause_views (plugin);
if (plugin->state != PLY_BOOT_SPLASH_DISPLAY_NORMAL)
hide_prompt (plugin);
plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL;
start_progress_animation (plugin);
redraw_views (plugin);
unpause_views (plugin);
}
static void
display_password (ply_boot_splash_plugin_t *plugin,
const char *prompt,
int bullets)
{
pause_views (plugin);
if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL)
stop_animation (plugin);
plugin->state = PLY_BOOT_SPLASH_DISPLAY_PASSWORD_ENTRY;
show_prompt (plugin, prompt, NULL, bullets);
redraw_views (plugin);
unpause_views (plugin);
}
static void
display_question (ply_boot_splash_plugin_t *plugin,
const char *prompt,
const char *entry_text)
{
pause_views (plugin);
if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL)
stop_animation (plugin);
plugin->state = PLY_BOOT_SPLASH_DISPLAY_QUESTION_ENTRY;
show_prompt (plugin, prompt, entry_text, -1);
redraw_views (plugin);
unpause_views (plugin);
}
static void
display_message (ply_boot_splash_plugin_t *plugin,
const char *message)
{
show_message (plugin, message);
}
ply_boot_splash_plugin_interface_t *
ply_boot_splash_plugin_get_interface (void)
{
static ply_boot_splash_plugin_interface_t plugin_interface =
{
.create_plugin = create_plugin,
.destroy_plugin = destroy_plugin,
.add_pixel_display = add_pixel_display,
.remove_pixel_display = remove_pixel_display,
.show_splash_screen = show_splash_screen,
.update_status = update_status,
.on_boot_progress = on_boot_progress,
.hide_splash_screen = hide_splash_screen,
.on_root_mounted = on_root_mounted,
.become_idle = become_idle,
.display_normal = display_normal,
.display_password = display_password,
.display_question = display_question,
.display_message = display_message,
.system_update = system_update,
};
return &plugin_interface;
}
/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */