Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 136 additions & 158 deletions default/plymouth/omarchy.script
Copy link
Contributor Author

@mlombardi96 mlombardi96 Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to all the formatting changes, it's easier to review this file with whitespace hidden.

Original file line number Diff line number Diff line change
@@ -1,116 +1,105 @@
# Omarchy Plymouth Theme Script

Window.SetBackgroundTopColor(0.101, 0.105, 0.149);
Window.SetBackgroundBottomColor(0.101, 0.105, 0.149);
Window.SetBackgroundBottomColor(0.101, 0.105, 0.149);

logo.image = Image("logo.png");
logo.sprite = Sprite(logo.image);
logo.sprite.SetX (Window.GetWidth() / 2 - logo.image.GetWidth() / 2);
logo.sprite.SetY (Window.GetHeight() / 2 - logo.image.GetHeight() / 2);
logo.sprite.SetOpacity (1);
logo.sprite.SetX(Window.GetWidth() / 2 - logo.image.GetWidth() / 2);
logo.sprite.SetY(Window.GetHeight() / 2 - logo.image.GetHeight() / 2);
logo.sprite.SetOpacity(1);

# Use these to adjust the progress bar timing
global.fake_progress_limit = 0.7; # Target percentage for fake progress (0.0 to 1.0)
global.fake_progress_duration = 15.0; # Duration in seconds to reach limit

# Progress bar animation variables
global.animation_frame = 0;
global.fake_progress = 0.0;
global.real_progress = 0.0;
global.fake_progress_active = 0; # 0 / 1 boolean
global.animation_frame = 0;
global.fake_progress_start_time = 0; # Track when fake progress started
global.fake_progress_active = 0;
global.fake_progress_start_time = 0.0; # Track when fake progress started
global.password_shown = 0; # Track if password dialog has been shown
global.max_progress = 0.0; # Track the maximum progress reached to prevent backwards movement

fun refresh_callback ()
{
global.animation_frame++;

# Animate fake progress to limit over time with easing
if (global.fake_progress_active == 1)
{
# Calculate elapsed time since start
elapsed_time = global.animation_frame / 50.0; # Convert frames to seconds (50 FPS)

# Calculate linear progress ratio (0 to 1) based on time
time_ratio = elapsed_time / global.fake_progress_duration;
if (time_ratio > 1.0)
time_ratio = 1.0;

# Apply easing curve: ease-out quadratic
# Formula: 1 - (1 - x)^2
eased_ratio = 1 - ((1 - time_ratio) * (1 - time_ratio));

# Calculate fake progress based on eased ratio
global.fake_progress = eased_ratio * global.fake_progress_limit;

# Update progress bar with fake progress
update_progress_bar(global.fake_progress);
}
}
fun refresh_callback() {
global.animation_frame++;

# Animate fake progress to limit over time with easing
if (global.fake_progress_active == 1) {
# Calculate elapsed time since start
elapsed_time = global.animation_frame / 50.0; # Convert frames to seconds (50 FPS)

Plymouth.SetRefreshFunction (refresh_callback);
# Calculate linear progress ratio (0 to 1) based on time
time_ratio = elapsed_time / global.fake_progress_duration;
if (time_ratio > 1.0) time_ratio = 1.0;

#----------------------------------------- Helper Functions --------------------------------
# Apply easing curve: ease-out quadratic
# Formula: 1 - (1 - x)^2
eased_ratio = 1 - ((1 - time_ratio) * (1 - time_ratio));

fun update_progress_bar(progress)
{
# Only update if progress is moving forward
if (progress > global.max_progress)
{
global.max_progress = progress;
width = Math.Int(progress_bar.original_image.GetWidth() * progress);
if (width < 1) width = 1; # Ensure minimum width of 1 pixel

progress_bar.image = progress_bar.original_image.Scale(width, progress_bar.original_image.GetHeight());
progress_bar.sprite.SetImage(progress_bar.image);
}
}
# Calculate fake progress based on eased ratio
global.fake_progress = eased_ratio * global.fake_progress_limit;

fun show_progress_bar()
{
progress_box.sprite.SetOpacity(1);
progress_bar.sprite.SetOpacity(1);
# Update progress bar with fake progress
update_progress_bar(global.fake_progress);
}
}

fun hide_progress_bar()
{
progress_box.sprite.SetOpacity(0);
progress_bar.sprite.SetOpacity(0);
}
Plymouth.SetRefreshFunction(refresh_callback);

fun show_password_dialog()
{
lock.sprite.SetOpacity(1);
entry.sprite.SetOpacity(1);
}
#----------------------------------------- Helper Functions --------------------------------

fun hide_password_dialog()
{
lock.sprite.SetOpacity(0);
entry.sprite.SetOpacity(0);
for (index = 0; bullet.sprites[index]; index++)
bullet.sprites[index].SetOpacity(0);
}
fun update_progress_bar(progress) {
# Only update if progress is moving forward
if (progress > global.max_progress) {
global.max_progress = progress;

width = Math.Int(progress_bar.original_image.GetWidth() * progress);
if (width < 1) width = 1; # Ensure minimum width of 1 pixel

fun start_fake_progress()
{
# Don't reset if we already have progress
if (global.max_progress == 0.0)
{
global.fake_progress = 0.0;
global.real_progress = 0.0;
update_progress_bar(0.0);
}
global.fake_progress_active = 1;
global.animation_frame = 0;
progress_bar.image = progress_bar.original_image.Scale(width, progress_bar.original_image.GetHeight());
progress_bar.sprite.SetImage(progress_bar.image);
}
}

fun show_progress_bar() {
progress_box.sprite.SetOpacity(1);
progress_bar.sprite.SetOpacity(1);
}

fun hide_progress_bar() {
progress_box.sprite.SetOpacity(0);
progress_bar.sprite.SetOpacity(0);
}

fun stop_fake_progress()
{
global.fake_progress_active = 0;
fun show_password_dialog() {
lock.sprite.SetOpacity(1);
entry.sprite.SetOpacity(1);
}

fun hide_password_dialog() {
lock.sprite.SetOpacity(0);
entry.sprite.SetOpacity(0);

for (index = 0; bullet.sprites[index]; index++) {
bullet.sprites[index].SetOpacity(0);
}
}

fun start_fake_progress() {
global.fake_progress_active = 1;

# Reset fake progress
global.animation_frame = 0;
global.max_progress = 0.0;
global.fake_progress = 0.0;
global.fake_progress_start_time = 0.0;
}

fun stop_fake_progress() {
global.fake_progress_active = 0;
}

#----------------------------------------- Dialogue --------------------------------

Expand All @@ -119,7 +108,7 @@ entry.image = Image("entry.png");
bullet.image = Image("bullet.png");

entry.sprite = Sprite(entry.image);
entry.x = Window.GetWidth()/2 - entry.image.GetWidth() / 2;
entry.x = Window.GetWidth() / 2 - entry.image.GetWidth() / 2;
entry.y = logo.sprite.GetY() + logo.image.GetHeight() + 40;
entry.sprite.SetPosition(entry.x, entry.y, 10001);
entry.sprite.SetOpacity(0);
Expand All @@ -133,65 +122,60 @@ lock_width = 84 * lock_scale;
scaled_lock = lock.image.Scale(lock_width, lock_height);
lock.sprite = Sprite(scaled_lock);
lock.x = entry.x - lock_width - 15;
lock.y = entry.y + entry.image.GetHeight()/2 - lock_height/2;
lock.y = entry.y + entry.image.GetHeight() / 2 - lock_height / 2;
lock.sprite.SetPosition(lock.x, lock.y, 10001);
lock.sprite.SetOpacity(0);

# Bullet array
bullet.sprites = [];

fun display_normal_callback ()
{
hide_password_dialog();

# Get current mode
mode = Plymouth.GetMode();

# Only show progress bar for boot and resume modes
if ((mode == "boot" || mode == "resume") && global.password_shown == 1)
{
show_progress_bar();
start_fake_progress();
}
fun display_normal_callback() {
hide_password_dialog();

# Get current mode
mode = Plymouth.GetMode();

# Only show progress bar for boot and resume modes
if ((mode == "boot" || mode == "resume") && global.password_shown == 1) {
show_progress_bar();
start_fake_progress();
}
}

fun display_password_callback (prompt, bullets)
{
global.password_shown = 1; # Mark that password dialog has been shown

# Reset progress when password dialog appears
stop_fake_progress();
hide_progress_bar();
global.max_progress = 0.0;
global.fake_progress = 0.0;
global.real_progress = 0.0;
show_password_dialog();

# Clear all bullets first
for (index = 0; bullet.sprites[index]; index++)
bullet.sprites[index].SetOpacity(0);

# Create and show bullets for current password (max 21)
max_bullets = 21;
bullets_to_show = bullets;
if (bullets_to_show > max_bullets)
bullets_to_show = max_bullets;

for (index = 0; index < bullets_to_show; index++)
{
if (!bullet.sprites[index])
{
# Scale bullet image to 7x7 pixels
scaled_bullet = bullet.image.Scale(7, 7);
bullet.sprites[index] = Sprite(scaled_bullet);
bullet.x = entry.x + 20 + index * (7 + 5);
bullet.y = entry.y + entry.image.GetHeight() / 2 - 3.5;
bullet.sprites[index].SetPosition(bullet.x, bullet.y, 10002);
}
bullet.sprites[index].SetOpacity(1);
}
fun display_password_callback(prompt, bullets) {
global.password_shown = 1; # Mark that password dialog has been shown

# Stop fake progress when password dialog appears
stop_fake_progress();
hide_progress_bar();
show_password_dialog();

# Clear all bullets first
for (index = 0; bullet.sprites[index]; index++) {
bullet.sprites[index].SetOpacity(0);
}

# Create and show bullets for current password (max 21)
max_bullets = 21;
bullets_to_show = bullets;
if (bullets_to_show > max_bullets) {
bullets_to_show = max_bullets;
}

for (index = 0; index < bullets_to_show; index++) {
if (!bullet.sprites[index]) {
# Scale bullet image to 7x7 pixels
scaled_bullet = bullet.image.Scale(7, 7);
bullet.sprites[index] = Sprite(scaled_bullet);
bullet.x = entry.x + 20 + index * (7 + 5);
bullet.y = entry.y + entry.image.GetHeight() / 2 - 3.5;
bullet.sprites[index].SetPosition(bullet.x, bullet.y, 10002);
}

bullet.sprites[index].SetOpacity(1);
}
}

Plymouth.SetDisplayNormalFunction(display_normal_callback);
Plymouth.SetDisplayPasswordFunction(display_password_callback);

Expand All @@ -214,44 +198,38 @@ progress_bar.y = progress_box.y + (progress_box.image.GetHeight() - progress_bar
progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 1);
progress_bar.sprite.SetOpacity(0);

fun progress_callback (duration, progress)
{
global.real_progress = progress;

# If real progress is above limit, stop fake progress and use real progress
if (progress > global.fake_progress_limit)
{
stop_fake_progress();
update_progress_bar(progress);
}
fun progress_callback(duration, progress) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main change. The progress_callback function is called several times a second throughout the boot process.

However, while the drive is being decrypted, the duration and progress arguments stop updating and are frozen until the drive is unlocked.

To handle this, we record the duration value the moment fake progress starts. Once the drive is unlocked, duration/progress will start updating again.

We switch to showing real progress once the fake progress values are exceeded so that there's a seamless handoff.

# Track when fake progress starts
# Needed because duration and progress freeze during drive decryption
if (global.fake_progress_start_time == 0.0) {
global.fake_progress_start_time = duration;
}

Plymouth.SetBootProgressFunction(progress_callback);

#----------------------------------------- Quit --------------------------------
global.real_progress = progress;

fun quit_callback ()
{
logo.sprite.SetOpacity (1);
# Use real progress once its unfrozen and exceeds fake progress
if (duration > global.fake_progress_start_time && progress > global.fake_progress) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I experimented with not tracking the starting duration at all and only having progress > global.fake_progress in this condition.

However, there are certain scenarios where the "frozen" real progress value is greater than the fake progress value as soon as the progress bar is shown, which results in only the real progress being shown the entire boot.

stop_fake_progress();
update_progress_bar(progress);
}
}

Plymouth.SetQuitFunction(quit_callback);
Plymouth.SetBootProgressFunction(progress_callback);

#----------------------------------------- Message --------------------------------

message_sprite = Sprite();
message_sprite.SetPosition(10, 10, 10000);

fun display_message_callback (text)
{
my_image = Image.Text(text, 1, 1, 1);
message_sprite.SetImage(my_image);
fun display_message_callback(text) {
message = Image.Text(text, 1, 1, 1);
message_sprite.SetImage(message);
message_sprite.SetOpacity(1);
}

fun hide_message_callback (text)
{
fun hide_message_callback(text) {
message_sprite.SetOpacity(0);
}

Plymouth.SetDisplayMessageFunction (display_message_callback);
Plymouth.SetHideMessageFunction (hide_message_callback);
Plymouth.SetDisplayMessageFunction(display_message_callback);
Plymouth.SetHideMessageFunction(hide_message_callback);
2 changes: 2 additions & 0 deletions migrations/1764272475.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
echo "Update Plymouth theme for a smoother progress bar animation"
omarchy-refresh-plymouth