atuq 0.2.0: branding real — el zip, el .ini, los binarios y un icono que no es de otro
Hash b3:7eff4ba4. La v0.1 dejó el branding fuera porque había que MIRAR el árbol antes de adivinar. Se miró, y lo que apareció decidió la forma de esta versión: - `application.ini` del appdir arranca diciendo «This file is not used». Es herencia del árbol de desarrollo: en un build empaquetado el launcher SÍ lo lee. `rebrand.py` no le cree al comentario: comprueba que las claves estén y falla si no. - Las cadenas que la gente VE están DENTRO de `browser/omni.ja` ⇒ sin re-empacar el zip no hay branding. Y el original no dice «Firefox», dice **Nightly**, porque construimos con `--with-branding=unofficial`. Sin tocarlo, atuq se presentaría como lo peor de los dos mundos. - Los ICONOS, en cambio, viven FUERA del zip ⇒ se reemplazan sin abrirlo. EL RE-EMPAQUE PRESERVA TRES PROPIEDADES DEL ORIGINAL, Y CADA UNA POR UNA RAZÓN MEDIDA: orden de las 5306 entradas (es el que Gecko lee al arrancar, y es lo que el jarlog del PGO va a refinar), `compress_type=0` (Mozilla lo deja SIN COMPRIMIR para poder mapearlo) y `date_time` 2010-01-01 (ya venía normalizado: Mozilla también persigue reproducibilidad). VERIFICADO CONTRA EL ARTEFACTO, NO CONTRA EL LOG: - diff entrada por entrada entre el omni.ja de firefox y el de atuq: **5306 → 5306, mismo orden, todo STORED, mismas fechas, y UNA SOLA entrada con contenido distinto**: brand.ftl. - re-empacar dos veces el mismo zip da el mismo sha256 ⇒ el paso es determinista. - application.ini: Vendor=tawasuyu, Name/RemotingName/CodeName=atuq. `ID` NO se toca: es el GUID con el que las extensiones declaran compatibilidad, y cambiarlo dejaría a atuq fuera del ecosistema de complementos. EL BuildID SE DERIVA DEL CONTENIDO DEL OVERLAY, no de la fecha. Es la clave con la que Gecko invalida su startup cache: si atuq cambia su chrome y el BuildID no se mueve, el navegador arranca con la interfaz vieja cacheada y PARECE que el overlay no agarró. Derivarlo del contenido lo mueve exactamente cuando hace falta y dos builds del mismo atuq dan el mismo número — con la fecha pasaría lo contrario en los dos sentidos. Se vio funcionar: al sumar `CodeName` el BuildID cambió solo, de 81921491609672 a 24128696592342. El icono se DIBUJA en código (PNG en Python puro, zlib + struct) y no se commitea como binario: un PNG en el árbol no se puede revisar en un diff, el código sí, y el resultado es idéntico en cada build. ⚠ Es un marcador de posición declarado —una marca geométrica plana, 5 tamaños—; lo correcto es que alguien dibuje el zorro. Su única función irrenunciable ya la cumple: que la ventana y el lanzador NO muestren el icono de otro producto. Binarios: firefox → atuq y firefox-bin → atuq-bin, con symlink `firefox` → `atuq` dentro del appdir porque hay scripts de terceros que invocan por el nombre histórico y Gecko resuelve su directorio por /proc/self/exe (entrar por el symlink resuelve al mismo sitio). Más `.desktop` con StartupWMClass=atuq, que casa con el app_id que da RemotingName bajo Wayland.
This commit is contained in:
+39
-21
@@ -15,14 +15,24 @@
|
||||
# vive en `recipes/atuq/` y se hashea por CONTENIDO con `ArtifactHash::of_tree`, igual que ya se
|
||||
# hacía con los `patches`. No hay commit que pinear y no hay fetch: editar un CSS mueve el hash.
|
||||
#
|
||||
# ══ LO QUE ESTA v0.1 NO HACE, A PROPÓSITO ══════════════════════════════════════════════════════
|
||||
# No renombra el binario ni toca `application.ini`. El branding necesita mirar la forma real del
|
||||
# árbol que sella `firefox` —qué es `firefox-bin`, cómo se resuelve el appdir— y adivinarlo desde
|
||||
# acá produciría un artefacto que arranca en la máquina de quien lo escribió y en ninguna otra.
|
||||
# Tampoco re-empaqueta `omni.ja`, que es donde vive el chrome de verdad; eso es la v0.2 y trae dos
|
||||
# condiciones ya sabidas (re-empacar determinista y preservar el orden del `jarlog` del PGO).
|
||||
# ══ v0.2: EL BRANDING VIVE DENTRO DEL ZIP ══════════════════════════════════════════════════════
|
||||
# La v0.1 dejó el branding fuera porque había que mirar el árbol real antes de adivinar. Ya se miró,
|
||||
# y lo que se encontró decide la forma de esta versión:
|
||||
#
|
||||
# · `application.ini` del appdir arranca con el comentario «This file is not used». Es HERENCIA
|
||||
# del árbol de desarrollo: en un build empaquetado el launcher SÍ lo lee. `rebrand.py` no le
|
||||
# cree al comentario, comprueba que las claves estén y falla si no.
|
||||
# · Las cadenas que la gente VE están dentro de `browser/omni.ja` (`brand.ftl`), así que sin
|
||||
# re-empacar el zip no hay branding posible. Y el original dice **Nightly**, no «Firefox»,
|
||||
# porque construimos con `--with-branding=browser/branding/unofficial`.
|
||||
# · Los ICONOS, en cambio, están FUERA del zip (`browser/chrome/icons/default/`) ⇒ se reemplazan
|
||||
# sin tocarlo.
|
||||
# · `browser/omni.ja` son 56 MB con TODAS sus entradas sin comprimir y con `date_time` ya
|
||||
# normalizado a 2010-01-01: Mozilla también persigue reproducibilidad. El re-empaque preserva
|
||||
# orden, método y fecha — el orden porque es el que Gecko lee al arrancar, y es lo que el
|
||||
# `jarlog` del PGO refinará cuando exista.
|
||||
name = "atuq"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
# El artefacto CONTIENE Firefox, así que hereda su licencia. El overlay de `recipes/atuq/` es
|
||||
# nuestro, pero eso no cambia lo que se distribuye.
|
||||
license = "MPL-2.0"
|
||||
@@ -51,34 +61,42 @@ DST=/out/usr/lib/atuq
|
||||
# hasta el final diciendo que todo fue bien (regla 3 del CLAUDE.md).
|
||||
[ -d "$SRC" ] || { echo "atuq: no está el árbol de firefox en $SRC — ¿la dep se materializó?" >&2; exit 1; }
|
||||
[ -x "$SRC/firefox" ] || { echo "atuq: $SRC/firefox no existe o no es ejecutable" >&2; exit 1; }
|
||||
[ -d "$SRC/browser" ] || { echo "atuq: falta $SRC/browser — el árbol no tiene la forma esperada" >&2; exit 1; }
|
||||
[ -f "$SRC/browser/omni.ja" ] || { echo "atuq: falta $SRC/browser/omni.ja" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$DST"
|
||||
cp -a "$SRC"/. "$DST"/
|
||||
|
||||
# ── LA CAPA DE atuq ───────────────────────────────────────────────────────────────────────────
|
||||
# ── LA CAPA DE CONFIGURACIÓN ──────────────────────────────────────────────────────────────────
|
||||
# `defaults/pref/` es el único sitio desde el que se puede pedir el autoconfig ANTES de que exista
|
||||
# un perfil; el resto cuelga de ahí.
|
||||
mkdir -p "$DST/defaults/pref" "$DST/chrome" "$DST/distribution"
|
||||
cp /src/prefs/autoconfig.js "$DST/defaults/pref/autoconfig.js"
|
||||
cp /src/atuq.cfg "$DST/atuq.cfg"
|
||||
cp /src/chrome/atuq.css "$DST/chrome/atuq.css"
|
||||
cp /src/prefs/autoconfig.js "$DST/defaults/pref/autoconfig.js"
|
||||
cp /src/atuq.cfg "$DST/atuq.cfg"
|
||||
cp /src/chrome/atuq.css "$DST/chrome/atuq.css"
|
||||
cp /src/distribution/policies.json "$DST/distribution/policies.json"
|
||||
|
||||
# El comando que teclea la gente. Apunta al binario SIN renombrar (ver la nota de la cabecera):
|
||||
# renombrarlo es branding y branding es la v0.2.
|
||||
mkdir -p /out/usr/bin
|
||||
ln -sf ../lib/atuq/firefox /out/usr/bin/atuq
|
||||
# ── EL BRANDING ───────────────────────────────────────────────────────────────────────────────
|
||||
# Todo lo que exige entrar al zip, editar el .ini o dibujar un PNG está en una herramienta aparte:
|
||||
# el shell no tiene `zip` en este lab, y `zipfile` además deja fijar orden, fecha y permisos, que
|
||||
# es de lo que depende que el artefacto reproduzca. La herramienta VERIFICA cada cambio y falla si
|
||||
# la entrada que iba a reemplazar no estaba.
|
||||
python3 /src/tools/rebrand.py "$DST" /src /out
|
||||
|
||||
# ── INVENTARIO: LO QUE SE MIRA CUANDO «ATUQ SE VE COMO FIREFOX» ───────────────────────────────
|
||||
# Los cuatro ficheros de la capa son el 100% de lo que distingue este artefacto de su base, y son
|
||||
# los que un `cp` silencioso podría no haber puesto. Se listan para que el log del build responda
|
||||
# solo la primera pregunta del diagnóstico.
|
||||
# ── INTEGRACIÓN CON EL ESCRITORIO ─────────────────────────────────────────────────────────────
|
||||
mkdir -p /out/usr/bin /out/usr/share/applications
|
||||
cp /src/atuq.desktop /out/usr/share/applications/atuq.desktop
|
||||
ln -sf ../lib/atuq/atuq /out/usr/bin/atuq
|
||||
|
||||
# ── INVENTARIO ────────────────────────────────────────────────────────────────────────────────
|
||||
# Lo que distingue este artefacto de su base, listado con tamaños, para que el log del build
|
||||
# responda solo la primera pregunta del diagnóstico cuando «atuq se ve como Firefox».
|
||||
echo "atuq: capa aplicada —"
|
||||
for f in defaults/pref/autoconfig.js atuq.cfg chrome/atuq.css distribution/policies.json; do
|
||||
[ -s "$DST/$f" ] || { echo "atuq: $f quedó VACÍO o ausente" >&2; exit 1; }
|
||||
echo " $f ($(wc -c < "$DST/$f") bytes)"
|
||||
done
|
||||
[ -x "$DST/atuq" ] || { echo "atuq: el binario renombrado no quedó ejecutable" >&2; exit 1; }
|
||||
grep -q '^Name=atuq' "$DST/application.ini" || { echo "atuq: application.ini no quedó rebrandeado" >&2; exit 1; }
|
||||
'''
|
||||
|
||||
[deps]
|
||||
@@ -86,4 +104,4 @@ done
|
||||
# dentro del artefacto de atuq y a partir de ahí atuq no lo necesita más. El precio es que el
|
||||
# artefacto pesa lo que pesa Firefox otra vez; la salida barata (una granja de symlinks) rompe la
|
||||
# resolución del appdir por /proc/self/exe, así que la v0.1 paga el disco y lo dice.
|
||||
build = ["firefox"]
|
||||
build = ["firefox", "python3"]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=atuq
|
||||
GenericName=Navegador web
|
||||
GenericName[en]=Web Browser
|
||||
Comment=Navegá la web con el navegador de la distro
|
||||
Comment[en]=Browse the web with the distro's browser
|
||||
Exec=atuq %u
|
||||
Icon=atuq
|
||||
Terminal=false
|
||||
Categories=Network;WebBrowser;
|
||||
StartupNotify=true
|
||||
StartupWMClass=atuq
|
||||
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;
|
||||
@@ -0,0 +1,16 @@
|
||||
# Reemplaza a `localization/en-US/branding/brand.ftl` dentro de `browser/omni.ja`.
|
||||
#
|
||||
# Éstas son las cadenas que la GENTE ve: el título de la ventana, «Acerca de», el menú. El original
|
||||
# de nuestra base dice «Nightly» —no «Firefox»— porque `recipes/firefox.toml` construye con
|
||||
# `--with-branding=browser/branding/unofficial`, cuyo nombre en clave es ése. Sin este fichero, atuq
|
||||
# se presentaría como Nightly, que es lo peor de los dos mundos: ni es Firefox ni es nuestro.
|
||||
#
|
||||
# `-brand-product-name` se mantiene distinto de `-brand-short-name` a propósito: hay cadenas de la
|
||||
# UI que hablan del PRODUCTO y otras de la MARCA, y aplanarlas produce frases raras en español.
|
||||
-brand-shorter-name = atuq
|
||||
-brand-short-name = atuq
|
||||
-brand-shortcut-name = atuq
|
||||
-brand-full-name = atuq
|
||||
-brand-product-name = atuq
|
||||
-vendor-short-name = tawasuyu
|
||||
trademarkInfo = { " " }
|
||||
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convierte el árbol de `firefox` ya copiado en el árbol de `atuq`.
|
||||
|
||||
Uso: rebrand.py <appdir> <overlay> <outroot>
|
||||
appdir = /out/usr/lib/atuq (la copia del árbol de firefox)
|
||||
overlay = /src (este directorio, `recipes/atuq/`)
|
||||
outroot = /out
|
||||
|
||||
POR QUÉ UNA HERRAMIENTA Y NO CUATRO LÍNEAS DE SHELL
|
||||
---------------------------------------------------
|
||||
El branding de verdad vive DENTRO de `browser/omni.ja`, que es un zip, y re-empacarlo desde shell
|
||||
exige `zip`, que el lab no trae. `zipfile` de la stdlib, además de estar disponible, da control
|
||||
sobre las tres cosas que el CLI no deja fijar y de las que depende que el artefacto REPRODUZCA:
|
||||
el orden de las entradas, su `date_time` y sus permisos.
|
||||
|
||||
TODO CAMBIO ES VERIFICADO, NINGUNO ES SILENCIOSO
|
||||
------------------------------------------------
|
||||
Cada reemplazo declara qué entrada espera encontrar y falla si no está. Un rebranding que no
|
||||
encuentra la cadena que iba a cambiar produce un navegador que se sigue llamando Nightly y sella
|
||||
igual de bien: sería exactamente la clase de fallo que llega hasta el final diciendo que todo fue
|
||||
bien (regla 3 del CLAUDE.md).
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
import zipfile
|
||||
|
||||
APPDIR, OVERLAY, OUTROOT = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
MARCA = "atuq"
|
||||
VENDOR = "tawasuyu"
|
||||
ACENTO = (0xB5, 0x65, 0x1D) # el mismo token que chrome/atuq.css
|
||||
CREMA = (0xF2, 0xE8, 0xD5)
|
||||
TAMANOS = (16, 32, 48, 64, 128)
|
||||
|
||||
|
||||
def fatal(msg):
|
||||
print(f"rebrand: {msg}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
# ── 1. La huella del overlay, que es también el BuildID ────────────────────────────────────────
|
||||
# El `BuildID` es la clave con la que Gecko invalida su startup cache. Si atuq cambia su chrome y
|
||||
# el BuildID no se mueve, el navegador arranca con la interfaz vieja cacheada y PARECE que el
|
||||
# overlay no agarró — el falso negativo que el SDD 26 §2 anota como gotcha del camino derivado.
|
||||
#
|
||||
# Se deriva del CONTENIDO del overlay y no de la fecha: cambia exactamente cuando cambia lo que
|
||||
# tiene que invalidar, y dos builds del mismo atuq dan el mismo número. La fecha haría lo contrario
|
||||
# en los dos sentidos.
|
||||
def huella_overlay():
|
||||
h = hashlib.sha256()
|
||||
for raiz, dirs, ficheros in os.walk(OVERLAY):
|
||||
dirs.sort()
|
||||
for f in sorted(ficheros):
|
||||
p = os.path.join(raiz, f)
|
||||
h.update(os.path.relpath(p, OVERLAY).encode())
|
||||
h.update(b"\0")
|
||||
with open(p, "rb") as fh:
|
||||
h.update(fh.read())
|
||||
h.update(b"\0")
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def build_id(huella):
|
||||
# 14 dígitos decimales: la forma que Gecko espera (nació de un `YYYYMMDDHHMMSS`).
|
||||
return str(int(huella[:16], 16))[:14].rjust(14, "0")
|
||||
|
||||
|
||||
# ── 2. application.ini — la identidad de la aplicación ─────────────────────────────────────────
|
||||
# ⚠ La cabecera de este fichero dice «This file is not used», y es un comentario HEREDADO del árbol
|
||||
# de desarrollo: en un build empaquetado como éste el launcher SÍ lo lee. Se comprueba en vez de
|
||||
# creerle al comentario — si no encontramos las claves, fallamos.
|
||||
#
|
||||
# `ID` NO se toca a propósito: es el GUID con el que las extensiones declaran compatibilidad
|
||||
# (`gecko.id` de Firefox). Cambiarlo convertiría a atuq en una aplicación desconocida para todo el
|
||||
# ecosistema de complementos, que es justo lo que un envoltorio no quiere.
|
||||
def rebrandear_application_ini(bid):
|
||||
ruta = os.path.join(APPDIR, "application.ini")
|
||||
if not os.path.isfile(ruta):
|
||||
fatal(f"no está {ruta}")
|
||||
with open(ruta, encoding="utf-8") as fh:
|
||||
lineas = fh.read().splitlines()
|
||||
# `CodeName` también: es el residuo de `--with-branding=unofficial` (dice «Nightly») y aparece
|
||||
# en about:buildconfig y en los informes de fallo. Dejarlo sería exactamente el rincón por el
|
||||
# que atuq se sigue presentando como otro producto.
|
||||
cambios = {
|
||||
"Vendor": VENDOR,
|
||||
"Name": MARCA,
|
||||
"RemotingName": MARCA,
|
||||
"CodeName": MARCA,
|
||||
"BuildID": bid,
|
||||
}
|
||||
vistas = set()
|
||||
salida = []
|
||||
for ln in lineas:
|
||||
clave = ln.split("=", 1)[0] if "=" in ln else None
|
||||
if clave in cambios:
|
||||
salida.append(f"{clave}={cambios[clave]}")
|
||||
vistas.add(clave)
|
||||
else:
|
||||
salida.append(ln)
|
||||
faltan = set(cambios) - vistas
|
||||
if faltan:
|
||||
fatal(f"application.ini no tenía las claves {sorted(faltan)} — ¿cambió el formato?")
|
||||
with open(ruta, "w", encoding="utf-8") as fh:
|
||||
fh.write("\n".join(salida) + "\n")
|
||||
return vistas
|
||||
|
||||
|
||||
# ── 3. omni.ja — el re-empaque determinista ────────────────────────────────────────────────────
|
||||
# TRES PROPIEDADES DEL ORIGINAL QUE SE PRESERVAN, Y POR QUÉ:
|
||||
#
|
||||
# · EL ORDEN de las entradas. Gecko lee este zip en el arranque y el orden es el que Mozilla
|
||||
# eligió para que las primeras lecturas caigan juntas. Reordenarlo (p.ej. alfabético, que es lo
|
||||
# que sale «gratis» al recomprimir) no rompe nada visible: sólo hace el arranque más lento, en
|
||||
# silencio. Cuando exista el PGO, el `jarlog` refina justamente este orden — tirarlo sería
|
||||
# tirar media optimización de arranque.
|
||||
# · `compress_type = 0` (STORED). El omni.ja de Mozilla va SIN COMPRIMIR a propósito, para poder
|
||||
# mapearlo en memoria. Comprimirlo achica el artefacto y encarece cada lectura.
|
||||
# · `date_time`. El original ya viene normalizado a 2010-01-01 —Mozilla también persigue
|
||||
# reproducibilidad— así que copiarlo tal cual mantiene el zip byte-idéntico salvo por lo que
|
||||
# cambiamos de verdad.
|
||||
def repack_omni(ruta_ja, reemplazos):
|
||||
if not os.path.isfile(ruta_ja):
|
||||
fatal(f"no está {ruta_ja}")
|
||||
pendientes = dict(reemplazos)
|
||||
tmp = ruta_ja + ".nuevo"
|
||||
with zipfile.ZipFile(ruta_ja) as orig:
|
||||
infos = orig.infolist()
|
||||
with zipfile.ZipFile(tmp, "w") as nuevo:
|
||||
for i in infos:
|
||||
datos = pendientes.pop(i.filename, None)
|
||||
if datos is None:
|
||||
datos = orig.read(i.filename)
|
||||
ni = zipfile.ZipInfo(i.filename, date_time=i.date_time)
|
||||
ni.compress_type = i.compress_type
|
||||
ni.external_attr = i.external_attr
|
||||
ni.create_system = i.create_system
|
||||
nuevo.writestr(ni, datos)
|
||||
if pendientes:
|
||||
os.unlink(tmp)
|
||||
fatal(f"{ruta_ja}: estas entradas no existían y no se reemplazó nada: {sorted(pendientes)}")
|
||||
os.replace(tmp, ruta_ja)
|
||||
return len(infos)
|
||||
|
||||
|
||||
# ── 4. El icono ────────────────────────────────────────────────────────────────────────────────
|
||||
# Se DIBUJA acá, en vez de commitear cinco PNG, por una razón práctica: un binario en el árbol de
|
||||
# fuentes no se puede revisar en un diff. El código sí, y el resultado es idéntico en cada build.
|
||||
#
|
||||
# ⚠ ES UN MARCADOR DE POSICIÓN, y conviene que se note: una marca geométrica plana, no un intento de
|
||||
# logo. Lo correcto es que alguien dibuje el zorro; hasta entonces esto cumple la única función que
|
||||
# no puede quedar vacía —que la ventana y el lanzador NO muestren el icono de otro producto—.
|
||||
def _en_rect_redondeado(px, py, n, r):
|
||||
cx = min(max(px, r), n - r)
|
||||
cy = min(max(py, r), n - r)
|
||||
dx, dy = px - cx, py - cy
|
||||
return dx * dx + dy * dy <= r * r
|
||||
|
||||
|
||||
def _en_triangulo(px, py, a, b, c):
|
||||
def signo(p, q, r):
|
||||
return (p[0] - r[0]) * (q[1] - r[1]) - (q[0] - r[0]) * (p[1] - r[1])
|
||||
d1, d2, d3 = signo((px, py), a, b), signo((px, py), b, c), signo((px, py), c, a)
|
||||
neg = d1 < 0 or d2 < 0 or d3 < 0
|
||||
pos = d1 > 0 or d2 > 0 or d3 > 0
|
||||
return not (neg and pos)
|
||||
|
||||
|
||||
def dibujar(size, ss=3):
|
||||
n = size * ss
|
||||
r = n * 0.22
|
||||
# La «oreja»: un triángulo centrado, con la punta arriba. A 16 px cualquier cosa con más de
|
||||
# tres vértices se convierte en barro, así que la forma es deliberadamente una sola.
|
||||
a = (n * 0.50, n * 0.26)
|
||||
b = (n * 0.74, n * 0.72)
|
||||
c = (n * 0.26, n * 0.72)
|
||||
filas = []
|
||||
for y in range(size):
|
||||
fila = []
|
||||
for x in range(size):
|
||||
acc = [0, 0, 0, 0]
|
||||
for sy in range(ss):
|
||||
for sx in range(ss):
|
||||
px, py = x * ss + sx + 0.5, y * ss + sy + 0.5
|
||||
if not _en_rect_redondeado(px, py, n, r):
|
||||
continue
|
||||
col = CREMA if _en_triangulo(px, py, a, b, c) else ACENTO
|
||||
acc[0] += col[0]
|
||||
acc[1] += col[1]
|
||||
acc[2] += col[2]
|
||||
acc[3] += 255
|
||||
m = ss * ss
|
||||
fila.append((acc[0] // m, acc[1] // m, acc[2] // m, acc[3] // m))
|
||||
filas.append(fila)
|
||||
return filas
|
||||
|
||||
|
||||
def png(filas):
|
||||
size = len(filas)
|
||||
crudo = b"".join(b"\x00" + bytes(v for px in f for v in px) for f in filas)
|
||||
|
||||
def trozo(tipo, datos):
|
||||
cuerpo = tipo + datos
|
||||
return struct.pack(">I", len(datos)) + cuerpo + struct.pack(">I", zlib.crc32(cuerpo) & 0xFFFFFFFF)
|
||||
|
||||
ihdr = struct.pack(">IIBBBBB", size, size, 8, 6, 0, 0, 0)
|
||||
return (b"\x89PNG\r\n\x1a\n" + trozo(b"IHDR", ihdr)
|
||||
+ trozo(b"IDAT", zlib.compress(crudo, 9)) + trozo(b"IEND", b""))
|
||||
|
||||
|
||||
def escribir_iconos():
|
||||
dir_app = os.path.join(APPDIR, "browser", "chrome", "icons", "default")
|
||||
if not os.path.isdir(dir_app):
|
||||
fatal(f"no está {dir_app} — el árbol de firefox no tiene la forma esperada")
|
||||
escritos = []
|
||||
for s in TAMANOS:
|
||||
datos = png(dibujar(s))
|
||||
# (a) el icono de la VENTANA, que Gecko lee del appdir
|
||||
p = os.path.join(dir_app, f"default{s}.png")
|
||||
with open(p, "wb") as fh:
|
||||
fh.write(datos)
|
||||
escritos.append(p)
|
||||
# (b) el icono del LANZADOR, que lo lee el escritorio por el tema hicolor
|
||||
q = os.path.join(OUTROOT, "usr/share/icons/hicolor", f"{s}x{s}", "apps", f"{MARCA}.png")
|
||||
os.makedirs(os.path.dirname(q), exist_ok=True)
|
||||
with open(q, "wb") as fh:
|
||||
fh.write(datos)
|
||||
escritos.append(q)
|
||||
return escritos
|
||||
|
||||
|
||||
# ── 5. Los binarios ────────────────────────────────────────────────────────────────────────────
|
||||
# Se renombran los dos y se deja un symlink `firefox` → `atuq` DENTRO del appdir. El symlink no es
|
||||
# indecisión: hay código y scripts de terceros que invocan el binario por su nombre histórico, y
|
||||
# como Gecko resuelve su directorio por `/proc/self/exe`, entrar por el symlink resuelve al mismo
|
||||
# sitio y no cambia nada del comportamiento.
|
||||
def renombrar_binarios():
|
||||
hechos = []
|
||||
for viejo, nuevo in (("firefox", MARCA), ("firefox-bin", f"{MARCA}-bin")):
|
||||
vp = os.path.join(APPDIR, viejo)
|
||||
if not os.path.isfile(vp):
|
||||
fatal(f"no está {vp}")
|
||||
os.rename(vp, os.path.join(APPDIR, nuevo))
|
||||
hechos.append(f"{viejo} → {nuevo}")
|
||||
os.symlink(MARCA, os.path.join(APPDIR, "firefox"))
|
||||
return hechos
|
||||
|
||||
|
||||
def main():
|
||||
huella = huella_overlay()
|
||||
bid = build_id(huella)
|
||||
claves = rebrandear_application_ini(bid)
|
||||
with open(os.path.join(OVERLAY, "branding", "brand.ftl"), "rb") as fh:
|
||||
brand = fh.read()
|
||||
n = repack_omni(
|
||||
os.path.join(APPDIR, "browser", "omni.ja"),
|
||||
{"localization/en-US/branding/brand.ftl": brand},
|
||||
)
|
||||
iconos = escribir_iconos()
|
||||
binarios = renombrar_binarios()
|
||||
|
||||
print(f"rebrand: overlay {huella[:16]}… ⇒ BuildID {bid}")
|
||||
print(f"rebrand: application.ini {sorted(claves)}")
|
||||
print(f"rebrand: browser/omni.ja re-empacado, {n} entradas, orden y STORED preservados")
|
||||
print(f"rebrand: {len(iconos)} iconos escritos ({', '.join(str(s) for s in TAMANOS)} px)")
|
||||
print(f"rebrand: binarios {', '.join(binarios)} (+ symlink firefox → {MARCA})")
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user