From 902bac59fefe8562b5cfc71a4a26c9c17aa69c26 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 15 Feb 2021 17:58:49 +0200 Subject: [PATCH] Add untracked files --- homeworld-theme/plymouth/homeworld.plymouth | 8 + homeworld-theme/plymouth/homeworld.script | 1064 +++++++++++++++++ .../plymouth_background_homeworld.png | Bin 0 -> 28620 bytes 3 files changed, 1072 insertions(+) create mode 100644 homeworld-theme/plymouth/homeworld.plymouth create mode 100644 homeworld-theme/plymouth/homeworld.script create mode 100644 homeworld-theme/plymouth/plymouth_background_homeworld.png diff --git a/homeworld-theme/plymouth/homeworld.plymouth b/homeworld-theme/plymouth/homeworld.plymouth new file mode 100644 index 0000000..04ee761 --- /dev/null +++ b/homeworld-theme/plymouth/homeworld.plymouth @@ -0,0 +1,8 @@ +[Plymouth Theme] +Name=Default theme for Debian 11.0 Buster (WIP) +Description=A theme inspired by the Bauhaus movement +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/homeworld +ScriptFile=/usr/share/plymouth/themes/homeworld/homeworld.script diff --git a/homeworld-theme/plymouth/homeworld.script b/homeworld-theme/plymouth/homeworld.script new file mode 100644 index 0000000..4874d72 --- /dev/null +++ b/homeworld-theme/plymouth/homeworld.script @@ -0,0 +1,1064 @@ +# homeworld.script - boot splash using script plugin +# +# Copyright (C) 2009 Canonical Ltd. +# Copyright © 2010-2016 Aurélien Couderc +# Copyright © 2014-2021 Juliette Taka +# +# 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: Alberto Milone +# +# Based on the example provided with the "script plugin" written by: +# Charlie Brej +# + + +#------------------------------- Constants ----------------------------------------- +NB_ROTATION_STEPS = 70; +NB_REFRESHS_BETWEEN_ANIMS = 3; + +# Initial position of the center of the logo in % of background +DEBIAN_POS_PCT.y = 0.65; # Debian image position in % of screen height +DEBIAN_HEIGHT_PCT = 0.07; # Debian image height in % of smallest screen dimension +LOGO_CENTER_PCT.x = 0.5; # Debian swirl image position in % of screen height & width +LOGO_CENTER_PCT.y = 0.442; +LOGO_SIZE_PCT = 0.18; # Debian swirl image size in % of smallest screen dimension + +#------------------------------- Globals ------------------------------------------- +# are we currently prompting for a password? +prompt_active = 0; + +# Globals to share progress time / percent with intersted functions +progress_time = 0; +progress_pct = 0; + +# Variables for glow rotation animation +anim_start_time = NULL; +anim_status = "stopped"; +refresh_iter = 0; +anim_iter = 0; + +#-----------------------------Text-image functions---------------------------- + +# Set the text colour in (rgb / 256) +text_colour.red = 1.0; +text_colour.green = 1.0; +text_colour.blue = 1.0; + +# Tinted text #988592 +tinted_text_colour.red = 1.0; +tinted_text_colour.green = 1.0; +tinted_text_colour.blue = 1.0; + +# Action Text - #ffffff - RGB 255 255 255 +action_text_colour.red = 1.0; +action_text_colour.green = 1.0; +action_text_colour.blue = 1.0; + +# Orange - #ff4012 - RGB 255 64 18 +debugsprite = Sprite(); +debugsprite_bottom = Sprite(); +debugsprite_bottom.SetPosition(0, (Window.GetHeight (0) - 20), 1); +debugsprite_medium = Sprite(); +debugsprite_medium.SetPosition(0, (Window.GetHeight (0) - 100), 1); + +# General purpose function to create text +fun WriteText (text, colour) { + image = Image.Text (text, colour.red, colour.green, colour.blue); + return image; +} + +fun ImageFromText (text) { + image = WriteText (text, text_colour); + return image; +} + +fun ImageFromTintedText (text) { + image = WriteText (text, tinted_text_colour); + return image; +} + +fun ImageFromActionText (text) { + image = WriteText (text, action_text_colour); + return image; +} + +fun Debug(text) { + debugsprite.SetImage(ImageFromText (text)); +} + +fun DebugBottom(text) { + debugsprite_bottom.SetImage(ImageFromText (text)); +} + +fun DebugMedium(text) { + debugsprite_medium.SetImage(ImageFromText (text)); +} + +#Debug("Window.GetHeight(0) = " + Window.GetHeight(0)); +fun TextYOffset() { + local.y; + local.text_height; + local.min_height; + + # Put the 1st line below the logo + some spacing + y = debian_sprite.GetY() + debian.GetHeight(); + #Debug("y = " + y); + + text_height = first_line_height * 7.5; + min_height = Window.GetHeight(); + #Debug("text_height=" + text_height + "; min_height=" + min_height); + + if (y + text_height > min_height) + y = min_height - text_height; + + return y; +} + + +#----------------------------- Screen/window setup --------------------------- +# Compute screen/image ratio and scale the background accordingly +window_max.width = Window.GetX() * 2 + Window.GetWidth(); +window_max.height = Window.GetY() * 2 + Window.GetHeight(); +screen_ratio = window_max.width / window_max.height; +small_dimension = Math.Min(window_max.width, window_max.height); +#Debug("Window.GetX():" + Window.GetX() + ", Window.GetY():" + Window.GetY()); +#Debug("Window is [" + window_max.width + ";" + window_max.height + "], ratio=" + screen_ratio); + +debian_height = small_dimension * DEBIAN_HEIGHT_PCT; +debian_pos.y = window_max.height * DEBIAN_POS_PCT.y - debian_height/2; +logo_size = small_dimension * LOGO_SIZE_PCT; +logo_center.x = window_max.width * LOGO_CENTER_PCT.x; +logo_center.y = window_max.height * LOGO_CENTER_PCT.y; +#Debug("Logo center at [" + logo_center.x + ";" + logo_center.y + "], size=" + logo_size + "px"); + +logo_pos.x = logo_center.x - logo_size/2; +logo_pos.y = logo_center.y - logo_size/2; + +#------------------------------- Background ---------------------------------------- +bg_image = Image("plymouth_background_homeworld.png"); +bg_image_ratio = bg_image.GetWidth() / bg_image.GetHeight(); +if (screen_ratio > bg_image_ratio) + bg_scale_factor = window_max.width / bg_image.GetWidth(); +else + bg_scale_factor = window_max.height / bg_image.GetHeight(); +scaled_bg_image = bg_image.Scale(bg_image.GetWidth() * bg_scale_factor, + bg_image.GetHeight() * bg_scale_factor); + +# Display background +bg_sprite = Sprite(scaled_bg_image); +bg_sprite.SetPosition(Window.GetX() + Window.GetWidth() / 2 - scaled_bg_image.GetWidth() / 2, + Window.GetY() + Window.GetHeight() / 2 - scaled_bg_image.GetHeight() / 2, + -1000); + +#------------------------------- Debian ---------------------------------------------- +debian = Image("debian.png"); +# Target same height as logo +debian_scale_factor = debian_height / debian.GetHeight(); +debian = debian.Scale(debian.GetWidth() * debian_scale_factor, + debian.GetHeight() * debian_scale_factor); +debian_sprite = Sprite(debian); +debian_sprite.SetPosition(window_max.width / 2 - debian.GetWidth() / 2, + debian_pos.y, + -90); + +#------------------------------- Logo ---------------------------------------------- +logo = Image("logo.png"); +logo_scale_factor = logo_size / logo.GetWidth(); +logo = logo.Scale(logo.GetWidth() * logo_scale_factor, + logo.GetHeight() * logo_scale_factor); +logo_to_top_edge = Window.GetHeight() * 0.3; +logo_sprite = Sprite(logo); +logo_sprite.SetPosition(logo_pos.x, logo_pos.y, -50); + + +logo_glow = Image("logo_circle.png"); +logo_glow_scale_factor = logo_size / logo_glow.GetWidth(); +logo_glow[0] = logo_glow.Scale(logo_glow.GetWidth() * logo_glow_scale_factor, + logo_glow.GetHeight() * logo_glow_scale_factor); +logo_glow_sprite = Sprite(); +logo_glow_sprite.SetPosition(logo_pos.x, logo_pos.y, -60); +logo_glow_sprite.SetImage(logo_glow[0]); + + + +#------------------------------String functions------------------------------- + +# This is the equivalent for strstr() +fun StringString(string, substring) { + start = 0; + while (String(string).CharAt (start)) { + walk = 0; + while (String(substring).CharAt (walk) == String(string).CharAt (start + walk) ) { + walk++; + if (!String(substring).CharAt (walk)) return start; + } + start++; + } + + return NULL; +} + +fun StringLength (string) { + index = 0; + while (String(string).CharAt(index)) index++; + return index; +} + +fun StringCopy (source, beginning, end) { + local.destination = ""; + for (index = beginning; ( ( (end == NULL) || (index <= end) ) && (String(source).CharAt(index)) ); index++) { + local.destination += String(source).CharAt(index); + } + + return local.destination; +} + +fun StringReplace (source, pattern, replacement) { + local.found = StringString(source, pattern); + if (local.found == NULL) + return source; + + local.new_string = StringCopy (source, 0, local.found - 1) + + replacement + + StringCopy (source, local.found + StringLength(pattern), NULL); + + return local.new_string; +} + +# it makes sense to use it only for +# numbers up to 100 +fun StringToInteger (str) { + int = -1; + for (i=0; i<=100; i++) { + if (i+"" == str) { + int = i; + break; + } + } + return int; +} + +#----------------------------------------------------------------------------- +# Top background colour +# #489291 --> 0.282, 0.572, 0.569 +# New background colour +# #0a3649 --> 0.039, 0.212, 0.286 +# +Window.SetBackgroundTopColor (0.282, 0.572, 0.569); # Nice colour on top of the screen fading to +Window.SetBackgroundBottomColor (0.039, 0.212, 0.286); # an equally nice colour on the bottom + +bits_per_pixel = Window.GetBitsPerPixel (); +# TODO need to handle 16 colors ? +#if (bits_per_pixel == 4) { +# logo_filename = "debian_logo16.png"; +# progress_dot_off_filename = "progress_dot_off16.png"; +# progress_dot_on_filename = "progress_dot_on16.png"; +# password_dot_filename = "password_dot.png"; +# password_field_filename = "password_field16.png"; +#} else { +# logo_filename = "debian_logo.png"; +# progress_dot_off_filename = "progress_dot_off.png"; +# progress_dot_on_filename = "progress_dot_on.png"; + password_dot_filename = "password_dot.png"; + password_field_filename = "password_field.png"; +#} + +message_notification[0].image = ImageFromTintedText (""); +message_notification[1].image = ImageFromTintedText (""); +fsck_notification.image = ImageFromActionText (""); + +status = "normal"; + +# use a fixed string with ascending and descending stems to calibrate the +# bounding box for the first message, so the messages below don't move up +# and down according to *their* height. +first_line_height = ImageFromTintedText ("AfpqtM").GetHeight(); + +# if the user has a 640x480 or 800x600 display, we can't quite fit everything +# (including passphrase prompts) with the target spacing, so scoot the text up +# a bit if needed. +top_of_the_text = TextYOffset(); + + +#-------------------------------Progress Indicator----------------------------- +# Implement in boot progress callback +fun animate_progress_indicator (time, progress) { + progress_time = time; + progress_pct = progress; + + #Debug ("mode = " + Plymouth.GetMode() + ", progress_time = " + progress_time + ", progress_pct = " + progress_pct); + +} + + +#-----------------------------------------Label utility functions--------------------- + +# label should be either a string or NULL +# Images for n lines will be created and returned as items of the +# message_label array +# +fun get_message_label (label, is_fake, is_action_line) { + #Debug("Get Label position"); + local.message_label; + + if (is_fake) + # Create a fake label so as to get the y coordinate of + # a standard-length label. + local.message_image = ImageFromTintedText ("This is a fake message"); + else + local.message_image = (is_action_line) && ImageFromActionText (label) || ImageFromTintedText (label); + + message_label.width = message_image.GetWidth (); + message_label.height = message_image.GetHeight (); + + # Center the line horizontally + message_label.x = Window.GetX () + Window.GetWidth () / 2 - message_label.width / 2; + + message_label.y = top_of_the_text; + + # Put the 2nd line below the fsck line + if (is_action_line) { + local.fsck_label.y = message_label.y + (first_line_height + first_line_height / 2); + message_label.y = local.fsck_label.y + (first_line_height * 1.5); + } + + #Debug("action label x = " + message_label.x + " y = " + message_label.y ); + +# message_debug = "msg_x = " + message_label.x + " msg_y = " + message_label.y + +# "msg_width = " + message_label.width + " msg_height = " + +# message_label.height + " message = " + label; +# Debug(message_debug); + + return message_label; + +} + +# Create an fsck label and/or get its position +fun get_fsck_label (label, is_fake) { + # Debug("Get Label position"); + local.fsck_label = global.progress_label; + + if (is_fake) + fsck_label.image = ImageFromTintedText ("This is a fake message"); + else + fsck_label.image = ImageFromTintedText (label); + + fsck_label.width = fsck_label.image.GetWidth (); + fsck_label.height = fsck_label.image.GetHeight (); + + # Centre the label horizontally + fsck_label.x = Window.GetX () + Window.GetWidth () / 2 - fsck_label.width / 2; + + local.first_label = get_message_label (label, 1, 0); + + # Place the label below the 1st message line + fsck_label.y = local.first_label.y + local.first_label.height + (local.first_label.height / 2); + +# message_debug = "msg_x = " + fsck_label.x + " msg_y = " + fsck_label.y + +# "msg_width = " + fsck_label.width + " msg_height = " + +# fsck_label.height + " message = " + label; +# Debug(message_debug); + + return fsck_label; +} + +#-----------------------------------------Message stuff -------------------------------- +# + +# Set up a message label +# +# NOTE: this is called when doing something like 'plymouth message "hello world"' +# +fun setup_message (message_text, x, y, z, index) { + #DebugMedium("Message setup: " + message_text); + global.message_notification[index].image = (index) && ImageFromActionText (message_text) || ImageFromTintedText (message_text); + + # Set up the text message, if any + message_notification[index].x = x; + message_notification[index].y = y; + message_notification[index].z = z; + + message_notification[index].sprite = Sprite (); + message_notification[index].sprite.SetImage (message_notification[index].image); + message_notification[index].sprite.SetX (message_notification[index].x); + message_notification[index].sprite.SetY (message_notification[index].y); + message_notification[index].sprite.SetZ (message_notification[index].z); + +} + +fun show_message (index) { + if (global.message_notification[index].sprite) global.message_notification[index].sprite.SetOpacity(1); +} + +fun hide_message (index) { + if (global.message_notification[index].sprite) global.message_notification[index].sprite.SetOpacity(0); +} + + + + +# the callback function is called when new message should be displayed. +# First arg is message to display. +fun message_callback (message) +{ + # Debug("Message callback"); + is_fake = 0; + if (!message || (message == "")) is_fake = 1; + + local.substring = "keys:"; + + # Look for the "keys:" prefix + local.keys = StringString(message, local.substring); + + local.is_action_line = (keys != NULL); + #Debug("keys " + local.keys + " substring length = " + StringLength(local.substring)); + + # Get the message without the "keys:" prefix + if (keys != NULL) + message = StringCopy (message, keys + StringLength(local.substring), NULL); + + # Get the message without the "fsckd-cancel-msg" prefix as we don't support i18n + substring = "fsckd-cancel-msg:"; + keys = StringString(message, substring); + if (keys != NULL) + message = StringCopy(message, keys + StringLength(substring), NULL); + + local.label.is_fake = is_fake; + label = get_message_label(message, is_fake, is_action_line); + label.z = 10000; + + setup_message (message, label.x, label.y, label.z, is_action_line); + if (prompt_active && local.is_action_line) + hide_message (is_action_line); + else + show_message (is_action_line); + +} + + +#-----------------------------------------Display Password stuff ----------------------- +# + +fun password_dialogue_setup (message_label) { + #Debug("Password dialog setup"); + + local.entry; + local.bullet_image; + + bullet_image = Image (password_dot_filename); + entry.image = Image (password_field_filename); + + # Hide the normal labels + prompt_active = 1; + if (message_notification[1].sprite) hide_message (1); + + # Set the prompt label + label = get_message_label(message_label, 0, 1); + label.z = 10000; + + setup_message (message_label, label.x, label.y, label.z, 2); + show_message (2); + + # Set up the text entry which contains the bullets + entry.sprite = Sprite (); + entry.sprite.SetImage (entry.image); + + # Centre the box horizontally + entry.x = Window.GetX () + Window.GetWidth () / 2 - entry.image.GetWidth () / 2; + + # Put the entry below the second label. + entry.y = message_notification[2].y + label.height * 1.5; + + #DebugMedium("entry x = " + entry.x + ", y = " + entry.y); + entry.z = 10000; + entry.sprite.SetX (entry.x); + entry.sprite.SetY (entry.y); + entry.sprite.SetZ (entry.z); + + global.password_dialogue = local; +} + +fun password_dialogue_opacity (opacity) { + #Debug("Setting password dialog opacity to " + opacity); + global.password_dialogue.opacity = opacity; + local = global.password_dialogue; + + # You can make the box translucent with a float + # entry.sprite.SetOpacity (0.3); + entry.sprite.SetOpacity (opacity); + label.sprite.SetOpacity (opacity); + + if (bullets) { + for (index = 0; bullets[index]; index++) { + bullets[index].sprite.SetOpacity (opacity); + } + } +} + + +# The callback function is called when the display should display a password dialogue. +# First arg is prompt string, the second is the number of bullets. +fun display_password_callback (prompt, bullets) { + #Debug("Password dialog setup"); + + global.status = "password"; + if (!global.password_dialogue) password_dialogue_setup(prompt); + password_dialogue_opacity (1); + bullet_width = password_dialogue.bullet_image.GetWidth(); + bullet_y = password_dialogue.entry.y + + password_dialogue.entry.image.GetHeight () / 2 - + password_dialogue.bullet_image.GetHeight () / 2; + margin = bullet_width; + spaces = Math.Int( (password_dialogue.entry.image.GetWidth () - (margin * 2)) / (bullet_width / 2 ) ); + #DebugMedium ("spaces = " + spaces + ", bullets = " + bullets); + bullets_area.width = margin + spaces * (bullet_width / 2); + bullets_area.x = Window.GetX () + Window.GetWidth () / 2 - bullets_area.width / 2; + #DebugBottom ("pwd_entry (x,y) = " + password_dialogue.entry.x + "," + password_dialogue.entry.y + # + "), bullets_area.x = " + bullets_area.x + ", bullets_area.width = " + bullets_area.width); + if (bullets > spaces) + bullets = spaces; + for (index = 0; password_dialogue.bullets[index] || index < bullets; index++){ + if (!password_dialogue.bullets[index]) { + password_dialogue.bullets[index].sprite = Sprite (); + password_dialogue.bullets[index].sprite.SetImage (password_dialogue.bullet_image); + password_dialogue.bullets[index].x = bullets_area.x + # password_dialogue.entry.x + margin + + index * bullet_width / 2; + password_dialogue.bullets[index].sprite.SetX (password_dialogue.bullets[index].x); + password_dialogue.bullets[index].y = bullet_y; + password_dialogue.bullets[index].sprite.SetY (password_dialogue.bullets[index].y); + password_dialogue.bullets[index].z = password_dialogue.entry.z + 1; + password_dialogue.bullets[index].sprite.SetZ (password_dialogue.bullets[index].z); + } + + password_dialogue.bullets[index].sprite.SetOpacity (0); + + if (index < bullets) { + password_dialogue.bullets[index].sprite.SetOpacity (1); + } + } +} + +Plymouth.SetDisplayPasswordFunction (display_password_callback); + +Plymouth.SetMessageFunction (message_callback); + +Plymouth.SetBootProgressFunction (animate_progress_indicator); + +# Plymouth.SetBootProgressFunction: the callback function is called with two numbers, the progress (between 0 and 1) and the time spent booting so far +# Plymouth.SetRootMountedFunction: the callback function is called when a new root is mounted +# Plymouth.SetKeyboardInputFunction: the callback function is called with a string containing a new character entered on the keyboard + +#----------------------------------------- FSCK Counter -------------------------------- + +# Initialise the counter +fun init_fsck_count () { + # The number of fsck checks in this cycle + global.counter.total = 0; + # The number of fsck checks already performed + the current one + global.counter.current = 1; + # The previous fsck + global.counter.last = 0; +} + +# Increase the total counter +fun increase_fsck_count () { + global.counter.total++; +} + +fun increase_current_fsck_count () { + global.counter.last = global.counter.current++; +} + +# Clear the counter +fun clear_fsck_count () { + global.counter = NULL; + init_fsck_count (); +} + +#----------------------------------------- Progress Label ------------------------------ + + +# Change the opacity level of a progress label +# +# opacity = 1 -> show +# opacity = 0 -> hide +# opacity = 0.3 (or any other float) -> translucent +# +fun set_progress_label_opacity (opacity) { + # the label + progress_label.sprite.SetOpacity (opacity); + + # Make the slot available again when hiding the bar + # So that another bar can take its place + if (opacity == 0) { + progress_label.is_available = 1; + progress_label.device = ""; + } +} + +# Set up a new Progress Bar +# +# TODO: Make it possible to reuse (rather than recreate) a bar +# if .is_available = 1. Ideally this would just reset the +# label, the associated +# device and the image size of the sprite. + +fun init_progress_label (device, status_string) { + # Make the slot unavailable + global.progress_label.is_available = 0; + progress_label.progress = 0; + progress_label.device = device; + progress_label.status_string = status_string; +} + +# See if the progress label is keeping track of the fsck +# of "device" +# +fun device_has_progress_label (device) { + #DebugBottom ("label device = " + progress_label.device + " checking device " + device); + return (progress_label.device == device); +} + +# Update the Progress bar which corresponds to index +# +fun update_progress_label (progress) { + # If progress is NULL then we just refresh the label. + # This happens when only counter.total has changed. + if (progress != NULL) { + progress_label.progress = progress; + + #Debug("device " + progress_label.device + " progress " + progress); + + # If progress >= 100% hide the label and make it available again + if (progress >= 100) { + set_progress_label_opacity (0); + + # See if we any other fsck check is complete + # and, if so, hide the progress bars and the labels + on_fsck_completed (); + + return 0; + } + } + # Update progress label here + # + # FIXME: the queue logic from this theme should really be moved into mountall + # instead of using string replacement to deal with localised strings. + label = StringReplace (progress_label.status_string[0], "%1$d", global.counter.current); + label = StringReplace (label, "%2$d", global.counter.total); + label = StringReplace (label, "%3$d", progress_label.progress); + label = StringReplace (label, "%%", "%"); + + progress_label = get_fsck_label (label, 0); + #progress_label.progress = progress; + + progress_label.sprite = Sprite (progress_label.image); + + # Set up the bar + progress_label.sprite.SetPosition(progress_label.x, progress_label.y, 1); + + set_progress_label_opacity (1); + +} + +# Refresh the label so as to update counters +fun refresh_progress_label () { + update_progress_label (NULL); +} + +#----------------------------------------- FSCK Queue ---------------------------------- + +# Initialise the fsck queue +fun init_queue () { + global.fsck_queue[0].device; + global.fsck_queue[0].progress; + global.fsck_queue.counter = 0; + global.fsck_queue.biggest_item = 0; +} + +fun clear_queue () { + global.fsck_queue = NULL; + init_queue (); +} + +# Return either the device index in the queue or -1 +fun queue_look_up_by_device (device) { + for (i=0; i <= fsck_queue.biggest_item; i++) { + if ((fsck_queue[i]) && (fsck_queue[i].device == device)) + return i; + } + return -1; +} + +# Keep track of an fsck process in the queue +fun add_fsck_to_queue (device, progress) { + # Look for an empty slot in the queue + for (i=0; global.fsck_queue[i].device; i++) { + continue; + } + local.index = i; + + # Set device and progress + global.fsck_queue[local.index].device = device; + global.fsck_queue[local.index].progress = progress; + + # Increase the queue counter + global.fsck_queue.counter++; + + # Update the max index of the array for iterations + if (local.index > global.fsck_queue.biggest_item) + global.fsck_queue.biggest_item = local.index; + + #DebugMedium ("Adding " + device + " at " + local.index); +} + +fun is_queue_empty () { + return (fsck_queue.counter == 0); +} + +fun is_progress_label_available () { + return (progress_label.is_available == 1); +} + + +# This should cover the case in which the fsck checks in +# the queue are completed before the ones showed in the +# progress label +fun on_queued_fsck_completed () { + if (!is_queue_empty ()) + return; + + # Hide the extra label, if any + #if (progress_bar.extra_label.sprite) + # progress_bar.extra_label.sprite.SetOpacity(0); +} + +fun remove_fsck_from_queue (index) { + # Free memory which was previously allocated for + # device and progress + global.fsck_queue[index].device = NULL; + global.fsck_queue[index].progress = NULL; + + # Decrease the queue counter + global.fsck_queue.counter--; + + # See if there are other processes in the queue + # if not, clear the extra_label + on_queued_fsck_completed (); +} + +fun on_fsck_completed () { + # We have moved on to tracking the next fsck + increase_current_fsck_count (); + + if (!is_progress_label_available ()) + return; + + if (!is_queue_empty ()) + return; + + # Hide the progress label + if (progress_label.sprite) + progress_label.sprite.SetOpacity (0); + + # Clear the queue + clear_queue (); + + # Clear the fsck counter + clear_fsck_count (); +} + +# Update an fsck process that we keep track of in the queue +fun update_progress_in_queue (index, device, progress) { + # If the fsck is complete, remove it from the queue + if (progress >= 100) { + remove_fsck_from_queue (index); + on_queued_fsck_completed (); + return; + } + + global.fsck_queue[index].device = device; + global.fsck_queue[index].progress = progress; + +} + +# TODO: Move it to some function +# Create an empty queue +#init_queue (); + + +#----------------------------------------- FSCK Functions ------------------------------ + + +# Either add a new bar for fsck checks or update an existing bar +# +# NOTE: no more than "progress_bar.max_number" bars are allowed +# +fun fsck_check (device, progress, status_string) { + + # The 1st time this will take place + if (!global.progress_label) { + # Increase the fsck counter + increase_fsck_count (); + + # Set up a new label for the check + init_progress_label (device, status_string); + update_progress_label (progress); + + return; + } + + + if (device_has_progress_label (device)) { + # Update the progress of the existing label + update_progress_label (progress); + } + else { + # See if there's already a slot in the queue for the device + local.queue_device_index = queue_look_up_by_device(device); + + # See if the progress_label is available + if (progress_label.is_available) { + +# local.my_string = "available index " + local.available_index + " progress_bar counter is " + progress_bar.counter; +# Debug(local.my_string); + + + # If the fsck check for the device was in the queue, then + # remove it from the queue + if (local.queue_device_index >= 0) { + remove_fsck_from_queue (index); + } + else { + # Increase the fsck counter + increase_fsck_count (); + } + +# local.my_string += local.message; + #Debug("setting new label for device " + device + " progress " + progress); + + # Set up a new label for the check + init_progress_label (device, status_string); + update_progress_label (progress); + + } + # If the progress_label is not available + else { + + # If the fsck check for the device is already in the queue + # just update its progress in the queue + if (local.queue_device_index >= 0) { + #DebugMedium("Updating queue at " + local.queue_device_index + " for device " + device); + update_progress_in_queue (local.queue_device_index, device, progress); + } + # Otherwise add the check to the queue + else { + #DebugMedium("Adding device " + device + " to queue at " + local.queue_device_index); + add_fsck_to_queue (device, progress); + + # Increase the fsck counter + increase_fsck_count (); + + refresh_progress_label (); + } + + } + } + +# if (!is_queue_empty ()) { +# DebugBottom("Extra label for "+ device); + #} +# else { +# DebugBottom("No extra label for " + device + ". 1st Device in the queue "+ fsck_queue[0].device + " counter = " + global.fsck_queue.counter); +# } +} + + +#-----------------------------------------Update Status stuff -------------------------- +# +# The update_status_callback is what we can use to pass plymouth whatever we want so +# as to make use of features which are available only in this program (as opposed to +# being available for any theme for the script plugin). +# +# Example: +# +# Thanks to the current implementation, some scripts can call "plymouth --update=fsck:sda1:40" +# and this program will know that 1) we're performing and fsck check, 2) we're checking sda1, +# 3) the program should set the label progress to 40% +# +# Other features can be easily added by parsing the string that we pass plymouth with "--update" +# +fun update_status_callback (status) { +# Debug(status); + if (!status) return; + + string_it = 0; + update_strings[string_it] = ""; + + for (i=0; (String(status).CharAt(i) != ""); i++) { + local.temp_char = String(status).CharAt(i); + if (temp_char != ":") + update_strings[string_it] += temp_char; + else + update_strings[++string_it] = ""; + } + +# my_string = update_strings[0] + " " + update_strings[1] + " " + update_strings[2]; +# Debug(my_string); + # Let's assume that we're dealing with these strings fsck:sda1:40 + if ((string_it >= 2) && (update_strings[0] == "fsck")) { + + device = update_strings[1]; + progress = update_strings[2]; + status_string[0] = update_strings[3]; # "Checking disk %1$d of %2$d (%3$d %% complete)" + if (!status_string[0]) + status_string[0] = "Checking disk %1$d of %2$d (%3$d %% complete)"; + + if ((device != "") && (progress != "")) { + progress = StringToInteger (progress); + + # Make sure that the fsck_queue is initialised + if (!global.fsck_queue) + init_queue (); + + # Make sure that the fsck counter is initialised + if (!global.counter) + init_fsck_count (); + +# if (!global.progress_bar.extra_label.sprite) +# create_extra_fsck_label (); + + # Keep track of the fsck check + fsck_check (device, progress, status_string); + } + + } + + # systemd-fsckd pass fsckd::: + if (update_strings[0] == "fsckd") { + number_devices = StringToInteger(update_strings[1]); + + if (number_devices > 0) { + label = update_strings[3]; + + progress_label = get_fsck_label (label, 0); + progress_label.sprite = Sprite (progress_label.image); + progress_label.sprite.SetPosition(progress_label.x, progress_label.y, 1); + progress_label.sprite.SetOpacity (1); + } else { + if (progress_label.sprite) + progress_label.sprite.SetOpacity (0); + } + } + +} +Plymouth.SetUpdateStatusFunction (update_status_callback); + +#-----------------------------------------Display Question stuff ----------------------- +# +# TODO: Implement this if needed +# +# The callback function is called when the display should display a question dialogue. +# First arg is prompt string, the second is the entry contents. +#fun display_question_callback (prompt_string, entry_contents) +#{ +# time++; +#} +# +#Plymouth.SetDisplayQuestionFunction (display_question_callback); + +fun rotate_img(source_img, current_step, nb_steps) { + angle = Math.Sin(current_step / nb_steps * Math.Pi / 2) * 2 * Math.Pi; + #debug_sin = Math.Sin(current_step / nb_steps * Math.Pi / 2); + #DebugMedium("Sin = " + debug_sin); + rotated_img = source_img.Rotate(angle); + return rotated_img; +} + +fun update_glow_anim () { + if (global.anim_start_time != global.progress_time && global.anim_status != "running") { + global.anim_start_time = global.progress_time; + global.anim_iter = 0; + global.anim_status = "running"; + } + + if (global.anim_status == "running") { + iter_img = global.logo_glow[global.anim_iter]; + if (iter_img == NULL) { + # Generate rotated image for the glow around the logo on demand. + #DebugMedium("Generating rotated image for index " + global.anim_iter); + iter_img = rotate_img(logo_glow[0], global.anim_iter, NB_ROTATION_STEPS); + global.logo_glow[global.anim_iter] = iter_img; + } + global.logo_glow_sprite.SetImage(iter_img); + global.anim_iter++; + + if (global.anim_iter >= NB_ROTATION_STEPS) { + global.anim_status = "stopped"; + } + } +} + +#-----------------------------------------Refresh stuff -------------------------------- +# +# Calling Plymouth.SetRefreshFunction with a function will set that function to be +# called up to 50 times every second, e.g. +# +# NOTE: if a refresh function is not set, Plymouth doesn't seem to be able to update +# the screen correctly +# +fun refresh_callback () +{ + global.refresh_iter++; + if (global.refresh_iter == NB_REFRESHS_BETWEEN_ANIMS) { + global.refresh_iter = 0; + update_glow_anim(); + } + #DebugBottom ("refresh_iter=" + refresh_iter + "; anim_status=" + anim_status + "; anim_start_time=" + anim_start_time + "; anim_iter=" + anim_iter); +} +Plymouth.SetRefreshFunction (refresh_callback); + + +#-----------------------------------------Display Normal stuff ----------------------- +# +# The callback function is called when the display should return to normal +fun display_normal_callback () +{ + global.status = "normal"; + if (global.password_dialogue) { + password_dialogue_opacity (0); + global.password_dialogue = NULL; + if (message_notification[2].sprite) hide_message(2); + prompt_active = 0; + } + + if (message_notification[1].sprite) { + show_message (1); + } + +} + +Plymouth.SetDisplayNormalFunction (display_normal_callback); + + +#----------------------------------------- Quit -------------------------------- + +fun quit_callback () +{ +} + +Plymouth.SetQuitFunction(quit_callback); diff --git a/homeworld-theme/plymouth/plymouth_background_homeworld.png b/homeworld-theme/plymouth/plymouth_background_homeworld.png new file mode 100644 index 0000000000000000000000000000000000000000..e6640a55be1918852ccf33d10e8ee007255d2875 GIT binary patch literal 28620 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 literal 0 HcmV?d00001