diff --git a/artwork/plymouth-center.png b/artwork/plymouth-center.png new file mode 100644 index 0000000..8960c14 Binary files /dev/null and b/artwork/plymouth-center.png differ diff --git a/artwork/plymouth-error.png b/artwork/plymouth-error.png new file mode 100644 index 0000000..38a355b Binary files /dev/null and b/artwork/plymouth-error.png differ diff --git a/artwork/plymouth-ring.png b/artwork/plymouth-ring.png new file mode 100644 index 0000000..b6558ac Binary files /dev/null and b/artwork/plymouth-ring.png differ diff --git a/artwork/plymouth-title.png b/artwork/plymouth-title.png new file mode 100644 index 0000000..a77da25 Binary files /dev/null and b/artwork/plymouth-title.png differ diff --git a/overlay/rootfs/etc/init.d/kill-plymouth b/overlay/rootfs/etc/init.d/kill-plymouth new file mode 100755 index 0000000..d9a672b --- /dev/null +++ b/overlay/rootfs/etc/init.d/kill-plymouth @@ -0,0 +1,15 @@ +#!/sbin/openrc-run + +name="Exit plymouthd" +description="Stop plymouthd before running the graphical UI" + +depend() { + before display-manager + before tinydm +} + +start() { + ebegin "Stopping boot splash" + /usr/bin/plymouth quit 2>/dev/null || true + eend 0 +} diff --git a/overlay/rootfs/etc/init.d/modemmanager b/overlay/rootfs/etc/init.d/modemmanager new file mode 100755 index 0000000..b02811a --- /dev/null +++ b/overlay/rootfs/etc/init.d/modemmanager @@ -0,0 +1,22 @@ +#!/sbin/openrc-run + +supervisor=supervise-daemon +command=/usr/sbin/ModemManager +command_args="${modemmanager_opts}" + +description="ModemManager Daemon" + +depend() { + need dbus + use polkit + after msm8909-modem-wwan-port + after msm-modem-uim-selection +} + +start_pre() { + killall -q ModemManager 2>/dev/null || true +} + +stop_post() { + killall -q ModemManager 2>/dev/null || true +} diff --git a/overlay/rootfs/etc/init.d/polkit b/overlay/rootfs/etc/init.d/polkit new file mode 100755 index 0000000..8554ebe --- /dev/null +++ b/overlay/rootfs/etc/init.d/polkit @@ -0,0 +1,12 @@ +#!/sbin/openrc-run +supervisor=supervise-daemon + +name="Polkit System Daemon" +description="Provide System authentication via PolicyKit1 D-Bus service" + +command=/usr/lib/polkit-1/polkitd +command_args="${POLKIT_OPTS:---no-debug --replace}" + +depend() { + need dbus +} diff --git a/overlay/rootfs/usr/libexec/msm-modem-uim-selection b/overlay/rootfs/usr/libexec/msm-modem-uim-selection index 8e6c57c..4b0158a 100755 --- a/overlay/rootfs/usr/libexec/msm-modem-uim-selection +++ b/overlay/rootfs/usr/libexec/msm-modem-uim-selection @@ -109,6 +109,8 @@ parse_uim_card_status() { BEGIN { prim_slot = 0 prim_app = 0 + sec_slot = 0 + sec_app = 0 cur_slot = 0 in_app = 0 for (s = 1; s <= 2; s++) { @@ -141,6 +143,20 @@ parse_uim_card_status() { prim_app = line + 0 } } + /Secondary GW:/ { + if ($0 ~ /slot \x27[0-9]+\x27/) { + line = $0 + sub(/.*slot \x27/, "", line) + sub(/\x27.*/, "", line) + sec_slot = line + 0 + } + if ($0 ~ /application \x27[0-9]+\x27/) { + line = $0 + sub(/.*application \x27/, "", line) + sub(/\x27.*/, "", line) + sec_app = line + 0 + } + } /^[[:space:]]*Slot \[[0-9]+\]:/ { line = $0 sub(/.*Slot \[/, "", line) @@ -239,7 +255,16 @@ parse_uim_card_status() { printf "PREFERRED_SLOT=%d\n", (pref == 2 ? 2 : 1) printf "PRIMARY_GW_SLOT=%d\n", prim_slot printf "PRIMARY_GW_APP=%d\n", prim_app + printf "SECONDARY_GW_SLOT=%d\n", sec_slot + printf "SECONDARY_GW_APP=%d\n", sec_app for (s = 1; s <= 2; s++) { + gsub(/[^A-Za-z0-9_.:-]/, "_", cstate[s]) + gsub(/[^A-Za-z0-9_.:-]/, "_", rawstate[s]) + gsub(/[^A-Za-z0-9_.:-]/, "_", app_type[s]) + gsub(/[^A-Za-z0-9_.:-]/, "_", app_state[s]) + gsub(/[^A-Za-z0-9_.:-]/, "", aid[s]) + gsub(/[^A-Za-z0-9_.:-]/, "_", pin1_state[s]) + gsub(/[^A-Za-z0-9_.:-]/, "_", upin_state[s]) printf "SLOT%d_STATE=%s\n", s, cstate[s] printf "SLOT%d_RAW_STATE=%s\n", s, rawstate[s] printf "SLOT%d_APP_TYPE=%s\n", s, app_type[s] @@ -355,6 +380,7 @@ parse_nas_serving_system() { printf "NAS_CS_STATE=%s\n", cs_state printf "NAS_PS_STATE=%s\n", ps_state printf "NAS_RAT=%s\n", rat + printf "NAS_RADIO=%s\n", rat printf "NAS_MCC=%s\n", mcc printf "NAS_MNC=%s\n", mnc printf "NAS_OPERATOR=%s\n", op_desc @@ -450,6 +476,10 @@ provision_slot() { return 0 fi + if [ "${SECONDARY_GW_SLOT:-0}" = "$chosen_slot" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true + fi + if [ "$PRIMARY_GW_SLOT" != "0" ]; then $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true fi @@ -481,6 +511,9 @@ prepare_slot_for_pin_op() { fi eval "target_aid=\$SLOT${slot}_AID" if [ "$PRIMARY_GW_SLOT" != "$slot" ]; then + if [ "${SECONDARY_GW_SLOT:-0}" = "$slot" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true + fi if [ "$PRIMARY_GW_SLOT" != "0" ]; then $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true fi @@ -490,12 +523,26 @@ prepare_slot_for_pin_op() { else $QMICLI_MODEM --uim-change-provisioning-session="slot=$slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true fi - sleep 0.6 + sleep 0.5 fi fi fi } +restart_modemmanager_clean() { + if command -v rc-service >/dev/null 2>&1; then + timeout 4 rc-service polkit start >/dev/null 2>&1 || true + timeout 6 rc-service modemmanager restart >/dev/null 2>&1 || { + killall -q ModemManager 2>/dev/null || true + sleep 0.3 + timeout 6 rc-service modemmanager zap >/dev/null 2>&1 || true + timeout 6 rc-service modemmanager start >/dev/null 2>&1 || true + } + elif command -v systemctl >/dev/null 2>&1; then + timeout 6 systemctl restart ModemManager >/dev/null 2>&1 || true + fi +} + enable_and_register_modem() { target_slot="${1:-$PREFERRED_SIM_SLOT}" force_unlock="${2:-0}" @@ -506,7 +553,7 @@ enable_and_register_modem() { i=0 mm_state="" - while [ "$i" -lt 10 ]; do + while [ "$i" -lt 8 ]; do mm_state="$(timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)" if [ -n "$mm_state" ]; then break @@ -515,12 +562,8 @@ enable_and_register_modem() { i=$((i + 1)) done - if [ "$mm_state" = "failed" ] || { [ "$force_unlock" = "1" ] && [ "$mm_state" = "locked" ]; }; then - if command -v rc-service >/dev/null 2>&1; then - timeout 6 rc-service modemmanager restart >/dev/null 2>&1 || true - elif command -v systemctl >/dev/null 2>&1; then - timeout 6 systemctl restart ModemManager >/dev/null 2>&1 || true - fi + if [ -z "$mm_state" ] || [ "$mm_state" = "failed" ] || { [ "$force_unlock" = "1" ] && [ "$mm_state" = "locked" ]; }; then + restart_modemmanager_clean i=0 mm_state="" while [ "$i" -lt 10 ]; do @@ -604,6 +647,15 @@ case "$cmd" in echo "PREFERRED_SLOT=$PREFERRED_SIM_SLOT" exit 0 fi + eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")" + if [ "$PRIMARY_GW_SLOT" = "0" ] && { [ "$SLOT1_STATE" = "present" ] || [ "$SLOT2_STATE" = "present" ]; }; then + SIM_WAIT_TIME=1 + provision_slot "$PREFERRED_SIM_SLOT" 2 >/dev/null 2>&1 || true + QMI_CARDS_NEW="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)" + if [ -n "$QMI_CARDS_NEW" ]; then + QMI_CARDS="$QMI_CARDS_NEW" + fi + fi echo "MODEM_AVAILABLE=1" parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT" QMI_NAS="$($QMICLI_MODEM --nas-get-serving-system 2>/dev/null || true)" @@ -622,11 +674,7 @@ case "$cmd" in save_preferred_slot "$target" SIM_WAIT_TIME=3 provision_slot "$target" 5 - if command -v rc-service >/dev/null 2>&1; then - timeout 6 rc-service modemmanager restart >/dev/null 2>&1 || true - elif command -v systemctl >/dev/null 2>&1; then - timeout 6 systemctl restart ModemManager >/dev/null 2>&1 || true - fi + restart_modemmanager_clean spawn_enable_and_register "$target" 0 exit 0 ;; @@ -639,30 +687,29 @@ case "$cmd" in exit 1 fi prepare_slot_for_pin_op "$slot" + eval "before_ret=\${SLOT${slot}_PIN1_RETRIES:-3}" verified=0 + wrong_pin=0 sim_id="$(timeout 3 mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)" - if [ -n "$sim_id" ] && timeout 4 mmcli -i "$sim_id" --pin="$pin" >/dev/null 2>&1; then - verified=1 + if [ -n "$sim_id" ]; then + mm_pin_out="$(timeout 5 mmcli -i "$sim_id" --pin="$pin" 2>&1)" && verified=1 || { + if printf "%s\n" "$mm_pin_out" | grep -qiE "IncorrectPin|SimPin|SimPuk|PinBlocked|incorrect"; then + wrong_pin=1 + fi + } fi - if [ "$verified" = "0" ] && [ -n "$QMICLI_MODEM" ]; then - if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then - verified=1 - elif $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then - verified=1 - elif $QMICLI_MODEM --uim-verify-pin="PIN1,$pin" >/dev/null 2>&1; then - verified=1 - elif $QMICLI_MODEM --uim-verify-pin="UPIN,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then - verified=1 - elif $QMICLI_MODEM --uim-verify-pin="UPIN,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then - verified=1 - elif $QMICLI_MODEM --dms-uim-verify-pin="PIN,$pin" >/dev/null 2>&1; then - verified=1 - fi - fi - if [ "$verified" = "0" ]; then - at_cpin="$("$0" at auto "AT+CPIN=\"$pin\"" 2>/dev/null || true)" - if printf "%s\n" "$at_cpin" | grep -qE "(^|[[:space:]])OK([[:space:]]|$)"; then - verified=1 + if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ] && [ -n "$QMICLI_MODEM" ]; then + qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" 2>&1)" && verified=1 || { + if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then + wrong_pin=1 + fi + } + if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then + qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" 2>&1)" && verified=1 || { + if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then + wrong_pin=1 + fi + } fi fi if [ "$verified" = "0" ] && [ -n "$QMICLI_MODEM" ]; then @@ -671,18 +718,24 @@ case "$cmd" in eval "$(parse_uim_card_status "$QMI_CARDS_AFTER" "$slot")" eval "after_pin1=\$SLOT${slot}_PIN1_STATE" eval "after_app=\$SLOT${slot}_APP_STATE" + eval "after_ret=\$SLOT${slot}_PIN1_RETRIES" if [ "$after_pin1" = "enabled-verified" ] || [ "$after_pin1" = "disabled" ] || [ "$after_app" = "ready" ]; then verified=1 + elif [ "$after_ret" -lt "$before_ret" ] 2>/dev/null; then + wrong_pin=1 fi fi fi + if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then + at_cpin="$("$0" at auto "AT+CPIN=\"$pin\"" 2>/dev/null || true)" + if printf "%s\n" "$at_cpin" | grep -qE "(^|[[:space:]])OK([[:space:]]|$)"; then + verified=1 + fi + fi if [ "$verified" = "1" ]; then save_preferred_slot "$slot" SIM_WAIT_TIME=2 provision_slot "$slot" 3 >/dev/null 2>&1 || true - if [ -n "$sim_id" ]; then - timeout 4 mmcli -i "$sim_id" --pin="$pin" >/dev/null 2>&1 || true - fi timeout 4 mmcli -m any -e >/dev/null 2>&1 || true spawn_enable_and_register "$slot" 1 exit 0 diff --git a/overlay/rootfs/usr/sbin/ModemManager b/overlay/rootfs/usr/sbin/ModemManager new file mode 100755 index 0000000..26c4fb4 Binary files /dev/null and b/overlay/rootfs/usr/sbin/ModemManager differ diff --git a/overlay/rootfs/usr/share/dbus-1/system-services/org.freedesktop.ModemManager1.service b/overlay/rootfs/usr/share/dbus-1/system-services/org.freedesktop.ModemManager1.service index e7445af..b542ba6 100644 --- a/overlay/rootfs/usr/share/dbus-1/system-services/org.freedesktop.ModemManager1.service +++ b/overlay/rootfs/usr/share/dbus-1/system-services/org.freedesktop.ModemManager1.service @@ -1,5 +1,5 @@ [D-BUS Service] Name=org.freedesktop.ModemManager1 -Exec=/usr/sbin/ModemManager --debug +Exec=/bin/false User=root SystemdService=dbus-org.freedesktop.ModemManager1.service diff --git a/overlay/rootfs/usr/share/plymouth/bizcom.png b/overlay/rootfs/usr/share/plymouth/bizcom.png new file mode 100644 index 0000000..9014f0c Binary files /dev/null and b/overlay/rootfs/usr/share/plymouth/bizcom.png differ diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/center.png b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/center.png new file mode 100644 index 0000000..8960c14 Binary files /dev/null and b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/center.png differ diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/error.png b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/error.png new file mode 100644 index 0000000..38a355b Binary files /dev/null and b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/error.png differ diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.plymouth b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.plymouth new file mode 100644 index 0000000..d37887c --- /dev/null +++ b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.plymouth @@ -0,0 +1,8 @@ +[Plymouth Theme] +Name=Bananarchy Spin +Description=Bananarchy Linux boot splash with rotating banana circle +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/pmos-spin +ScriptFile=/usr/share/plymouth/themes/pmos-spin/pmos-spin.script diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.script b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.script new file mode 100644 index 0000000..e56c3c9 --- /dev/null +++ b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/pmos-spin.script @@ -0,0 +1,137 @@ +# Bananarchy Linux Plymouth Theme Script +# Central crossed bananas ("A") remain stationary while the outer banana circle rotates. + +Window.SetBackgroundTopColor(0, 0, 0); +Window.SetBackgroundBottomColor(0, 0, 0); + +center_image = Image("center.png"); +ring_image = Image("ring.png"); +error_image = Image("error.png"); +title_image = Image("title.png"); + +win_w = Window.GetWidth(); +win_h = Window.GetHeight(); + +target_size = Math.Min(win_w * 0.72, win_h * 0.56); +if (target_size > 164 && target_size < 176) +{ + scale_ratio = 1.0; +} +else +{ + scale_ratio = target_size / center_image.GetWidth(); + center_image = center_image.Scale( + center_image.GetWidth() * scale_ratio, + center_image.GetHeight() * scale_ratio + ); + ring_image = ring_image.Scale( + ring_image.GetWidth() * scale_ratio, + ring_image.GetHeight() * scale_ratio + ); + error_image = error_image.Scale( + error_image.GetWidth() * scale_ratio, + error_image.GetHeight() * scale_ratio + ); + title_image = title_image.Scale( + title_image.GetWidth() * scale_ratio, + title_image.GetHeight() * scale_ratio + ); +} + +global.message_font = "Sans 10"; + +logo_x = Window.GetX() + win_w / 2 - center_image.GetWidth() / 2; +logo_y = Window.GetY() + win_h / 2 - center_image.GetHeight() / 2 - 16; + +title_x = Window.GetX() + win_w / 2 - title_image.GetWidth() / 2; +title_y = logo_y + center_image.GetHeight() + 12; + +ring_sprite = Sprite(ring_image); +ring_sprite.SetPosition(logo_x, logo_y, 10); +ring_sprite.SetOpacity(1); + +center_sprite = Sprite(center_image); +center_sprite.SetPosition(logo_x, logo_y, 20); +center_sprite.SetOpacity(1); + +title_sprite = Sprite(title_image); +title_sprite.SetPosition(title_x, title_y, 20); +title_sprite.SetOpacity(1); + +error_sprite = Sprite(error_image); +error_sprite.SetPosition(logo_x, logo_y, 30); +error_sprite.SetOpacity(0); + +global.angle = 0.0; +global.two_pi = 6.283185307; +global.status = "normal"; + +fun refresh_callback() +{ + if (status == "error") + { + ring_sprite.SetOpacity(0); + center_sprite.SetOpacity(0); + error_sprite.SetOpacity(1); + return; + } + + error_sprite.SetOpacity(0); + ring_sprite.SetOpacity(1); + center_sprite.SetOpacity(1); + + angle = angle + 0.055; + if (angle >= two_pi) + { + angle = angle - two_pi; + } + ring_sprite.SetImage(ring_image.Rotate(angle)); +} + +Plymouth.SetRefreshFunction(refresh_callback); + +message_sprite = Sprite(); +message_sprite.SetPosition(10, 10, 10000); + +fun display_message_callback(text) +{ + if (status == "error") + text_image = Image.Text(text, 1, 0.3, 0.2, 1, message_font, "center"); + else if (status == "warning") + text_image = Image.Text(text, 1, 0.84, 0.04, 1, message_font, "center"); + else + text_image = Image.Text(text, 0.8, 0.8, 0.8, 0.9, message_font, "center"); + + message_sprite.SetImage(text_image); + message_x = Window.GetX() + win_w / 2 - text_image.GetWidth() / 2; + message_y = Window.GetY() + win_h - text_image.GetHeight() - 12; + message_sprite.SetPosition(message_x, message_y, 10000); +} + +fun hide_message_callback(text) +{ + message_sprite = Sprite(); + message_sprite.SetPosition(10, 10, 10000); + status = "normal"; +} + +Plymouth.SetDisplayMessageFunction(display_message_callback); +Plymouth.SetHideMessageFunction(hide_message_callback); + +fun update_status_callback(new_status) +{ + if (new_status == "warning" || new_status == "error") + global.status = new_status; + else + global.status = "normal"; +} + +Plymouth.SetUpdateStatusFunction(update_status_callback); + +fun quit_callback() +{ + ring_sprite.SetOpacity(1); + center_sprite.SetOpacity(1); +} + +Plymouth.SetQuitFunction(quit_callback); diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/ring.png b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/ring.png new file mode 100644 index 0000000..b6558ac Binary files /dev/null and b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/ring.png differ diff --git a/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/title.png b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/title.png new file mode 100644 index 0000000..a77da25 Binary files /dev/null and b/overlay/rootfs/usr/share/plymouth/themes/pmos-spin/title.png differ diff --git a/scripts/generate-artwork.py b/scripts/generate-artwork.py index 3598eed..7468150 100755 --- a/scripts/generate-artwork.py +++ b/scripts/generate-artwork.py @@ -140,7 +140,101 @@ def main(): splash.save(os.path.join(ARTWORK_DIR, "splash-240x320.png"), "PNG", optimize=True) splash.save(os.path.join(ARTWORK_DIR, "splash-240x320.ppm"), "PPM") - # 9. Project documentation banner (800x240) + # 9. Plymouth rotating boot splash layers (stationary central A + rotating outer ring) + import numpy as np + from scipy import ndimage + src_rgba = src.convert("RGBA") + arr = np.array(src_rgba) + rgb = arr[:, :, :3].astype(np.float32) + r_ch, g_ch, b_ch = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] + lum = rgb.max(axis=2) + + # Separate central A bananas from outer banana circle via distance transform + is_yellow = (r_ch > 140) & (g_ch > 90) & (b_ch < 90) + labeled, num = ndimage.label(is_yellow) + center_yellow = np.zeros_like(is_yellow) + outer_yellow = np.zeros_like(is_yellow) + for i in range(1, num + 1): + pts = np.argwhere(labeled == i) + if len(pts) < 50: + continue + ymin, xmin = pts.min(axis=0) + ymax, xmax = pts.max(axis=0) + yc, xc = pts.mean(axis=0) + rc = np.hypot(xc - 512.0, yc - 512.0) + if (xmax - xmin) > 700 or rc > 280.0: + outer_yellow |= (labeled == i) + else: + center_yellow |= (labeled == i) + + dist_center = ndimage.distance_transform_edt(~center_yellow) + dist_outer = ndimage.distance_transform_edt(~outer_yellow) + y_grid, x_grid = np.ogrid[:1024, :1024] + rad = np.hypot(x_grid - 512.0, y_grid - 512.0) + fg_alpha = np.clip((lum - 10.0) / 28.0, 0.0, 1.0) + + in_inner_core = np.clip((290.0 - rad) / 4.0, 0.0, 1.0) * np.clip((718.0 - y_grid) / 4.0, 0.0, 1.0) + near_center_tip = ( + np.clip((dist_outer - dist_center + 4.0) / 4.0, 0.0, 1.0) + * np.clip((27.0 - dist_center) / 3.0, 0.0, 1.0) + * np.clip((y_grid - 91.0) / 2.5, 0.0, 1.0) + * np.clip((722.0 - y_grid) / 3.0, 0.0, 1.0) + ) + center_weight = np.maximum(in_inner_core, near_center_tip) + ring_weight = 1.0 - center_weight + + center_rgba = arr.copy() + center_rgba[:, :, 3] = (fg_alpha * center_weight * 255.0).astype(np.uint8) + ring_rgba = arr.copy() + ring_rgba[:, :, 3] = (fg_alpha * ring_weight * 255.0).astype(np.uint8) + + err_rgba = arr.copy() + err_rgba[:, :, 0] = np.clip(rgb[:, :, 0] * 1.1, 0, 255).astype(np.uint8) + err_rgba[:, :, 1] = (rgb[:, :, 1] * 0.22).astype(np.uint8) + err_rgba[:, :, 2] = (rgb[:, :, 2] * 0.18).astype(np.uint8) + err_rgba[:, :, 3] = (fg_alpha * 255.0).astype(np.uint8) + + ply_size = 168 + ply_center_img = Image.fromarray(center_rgba).resize((ply_size, ply_size), Image.Resampling.LANCZOS) + ply_ring_img = Image.fromarray(ring_rgba).resize((ply_size, ply_size), Image.Resampling.LANCZOS) + ply_error_img = Image.fromarray(err_rgba).resize((ply_size, ply_size), Image.Resampling.LANCZOS) + + # Title image for Plymouth splash ("BANANARCHY") + ply_title_img = Image.new("RGBA", (180, 28), (0, 0, 0, 0)) + ptdraw = ImageDraw.Draw(ply_title_img) + ptb = ptdraw.textbbox((0, 0), title_text, font=font_title) + ptw = ptb[2] - ptb[0] + ptdraw.text(((180 - ptw) // 2, 2), title_text, fill=(255, 214, 10, 255), font=font_title) + + ply_center_img.save(os.path.join(ARTWORK_DIR, "plymouth-center.png"), "PNG", optimize=True) + ply_ring_img.save(os.path.join(ARTWORK_DIR, "plymouth-ring.png"), "PNG", optimize=True) + ply_error_img.save(os.path.join(ARTWORK_DIR, "plymouth-error.png"), "PNG", optimize=True) + ply_title_img.save(os.path.join(ARTWORK_DIR, "plymouth-title.png"), "PNG", optimize=True) + + for root_base in ( + os.path.join(BASE_DIR, "overlay", "rootfs"), + os.path.join(BASE_DIR, "rootfs"), + ): + if not os.path.isdir(root_base): + continue + ply_dir = os.path.join(root_base, "usr", "share", "plymouth") + theme_dir = os.path.join(ply_dir, "themes", "pmos-spin") + os.makedirs(theme_dir, exist_ok=True) + ply_center_img.save(os.path.join(theme_dir, "center.png"), "PNG", optimize=True) + ply_ring_img.save(os.path.join(theme_dir, "ring.png"), "PNG", optimize=True) + ply_error_img.save(os.path.join(theme_dir, "error.png"), "PNG", optimize=True) + ply_title_img.save(os.path.join(theme_dir, "title.png"), "PNG", optimize=True) + for stale in ("right.png", "top.png", "left.png"): + stale_path = os.path.join(theme_dir, stale) + if os.path.exists(stale_path): + os.remove(stale_path) + icon_rgba = Image.fromarray(arr.copy()) + icon_rgba.putalpha(Image.fromarray((fg_alpha * 255.0).astype(np.uint8))) + icon_rgba.resize((128, 128), Image.Resampling.LANCZOS).save( + os.path.join(ply_dir, "bizcom.png"), "PNG", optimize=True + ) + + # 10. Project documentation banner (800x240) banner = make_canvas(800, 240, (1, 1, 1)) paste_centered(banner, src, 204, 118, 120) bdraw = ImageDraw.Draw(banner)