KOReader na čítačke Amazon Kindle - inštalácia, skúsenosti

14.05 | 20:22 | Mirecove dristy | Miroslav Bendík

Dnešný článok bude venovaný open source čítačke KOReader. V článku sa venujem inštalácii, používaniu a porovnaniu so štandardnou čítačkou na Amazon Kindle Paperwhite 2 (drvivá väčšina platí aj pre ostatné čítačky od Amazonu). Ďalej sa venujem optimalizácii spotrebovanej RAM a riešeniu problémov pri spustení bez Kindle Framweorku. Článok uzatváram trochou disassembleru a malým pluginom pre lepšiu podporu Kindle.

Vstavaná čítačka od Amazonu je pomerne spoľahlivá, ale má minimálne možnosti nastavenia, text nezobrazuje práve najlepšie a človek občas narazí na nejaký nepríjemný bug, napríklad, že si čítačka nepamätá stranu pri použití vlastných fontov.

Inštalácia KOReadera

Po jailbreaku a rozbehaní KUAL je inštalácia extrémne jednoduchá. Stačí stiahnuť balík pre Kindle a skopírovať adresár koreader a obsah adresára extensions do čítačky. Teraz stačí už len spustiť KUAL menu a ťuknúť na KOReader.

Výhody vstavanej čítačky

Vstavaná čítačka je predinštalovaná v systéme. Používateľ dostane aký-taký základ bez nutnosti čokoľvek nastavovať, alebo riešiť. Knihy sa dajú bez problémov skopírovať cez USB mass storage (aj keď je nutná konverzia do podporovaného formátu).

Knižnica je užívateľsky prívetivá. Knihy môžu byť organizované do zbierok, dajú sa filtrovať a vyhľadávať. Užívateľ vôbec nevidí adresárovú štruktúru. Domovská stránka zobrazuje štandardne posledné otvorené knihy.

Zobrazenie kníh nie je úplne najlepšie. Text sa renderuje strašne. Čo môžem jemne pochváliť rýchlosť zoomu a posunu v PDF dokumentoch.

V čom je iný KOReader

Existuje mnoho dôvodov, prečo by niekto mohol chcieť používať alternatívnu čítačku. V tejto sekcii skúsim zhrnúť zmeny oproti vstavanej čítačke, výhody a nevýhody.

Zobrazenie knihy

Začnem pre mňa najdôležitejším bodom - zobrazenie knihy. Kvalita (alebo nekvalita) sadzby textu dokáže ovplyvniť rýchlosť a celkový dojem z čítania. V nasledujúcom texte porovnávam zobrazenie textu v oboch čítačkách.

Delenie slov

Teoreticky delenie slov podporujú obe čítačky. Kindle však vyžaduje vloženie znaku soft hyphen na každé miesto, kde sa dá slovo rozdeliť. KOReader má vstavané slovníky, takže ak má kniha správne nastavený jazyk, dokáže rozdeliť slová automaticky.

Okrem delenia slov sa dajú v KOReaderi nastaviť aj veľkosti medzier medzi slovami a pravidlá pre minimálnu vzdialenosť slov (redukciu medzier). Vo výsledku je KOReader schopný udržať aj pri pomerne krátkych riadkoch konzistentnú veľkosť medzier. U Kindle je naopak rozdiel medzi štandardnou medzerou (riadok hypothetical questions) a najhorším riadkom (scientists …) obrovský.

Delenie slov
Obrázok 1: Delenie slov

Mimochodom soft hyphen sa dá pridať v calibre cez dialóg Polish books (klávesová skratka p) a výber Add soft hyphens.

Pridanie soft hyphens
Obrázok 2: Pridanie soft hyphens

Kerning

Medzery medzi znakmi by mali mať konzistentnú veľkosť. Pri výpočte šírky medzery sa musí brať do úvahy tvar písmen. Napríklad písmena AV by mali byť k sebe bližšie než je ich typická šírka, pretože ich tvar do seba pekne zapadne. Fonty majú v sebe zvyčajne tabuľku obsahujúcu horizontálny posun znakov oproti štandardnému posunu pre každú dvojicu znakov, ktorá do seba nejak zapadne. Kindle v podpore kerningu absolútne zlyháva.

Teraz sa pozrime na príklad kerningu s rovnakým písmom. Do pozornosti dávam napríklad dvojicu ou v slove out, hy v slove physics, alebo nt v slove eventually.

Kerning
Obrázok 3: Kerning

Ligatúry

Niektoré skupiny znakov sa môžu zobrazovať ako jeden znak. Typický príklad je dvojica fi, kde sa vrchná časť znaku f prekrýva s bodkou nad znakom i. KOReader pekne podporuje ligatúry, ale Kindle zase absolútne bez podpory.

Ligatúry
Obrázok 4: Ligatúry

Hinting

Ako na poslednú vlastnosť zobrazenia textu sa pozrieme na hinting. Ide o úpravu kontrolných bodov fontu tak, aby sa presne zarovnali do mriežky pixelov. Horizontálne a vertikálne hrany sú vďaka tomu ostrejšie. Zmenu pozície bodov riadi bytekód, ktorý sa interpretuje. Niektoré fonty sú navrhnuté tak, že bez hintingu sa nezobrazujú korektne. Opäť Kindle nemá žiadnu podporu hintingu. V čítačke KOReader sa dá hinting voliteľne zapnúť.

Hinting
Obrázok 5: Hinting

Stránkovanie

Obe čítačky majú iný algoritmus stránkovania. Kindle považuje za stránku určitý pevne daný počet znakov. Zmena veľkosti, alebo typu písma teda nemení počet stránok. Pri čítaní podľa nastavenej veľkosti písma sa niekedy zmení strana o 2 čísla, inokedy o 0.

KOReader čísluje omnoho presnejšie, ale zároveň to znamená, že pri zmene písma sa musí viac-menej vyrenderovať celá kniha. Kindle preto vie meniť veľkosť písma prakticky okamžite, pretože odhad počtu stránok sa pri tejto operácii nemení.

Fonty

Ako som na začiatku spomenul, Kindle nefunguje dobre s inými fontmi než so vstavanými kvôli bugu, pri ktorom si napamätá aktuálnu pozíciu. Zo vstavaných fontov sa mi najlepšie číta Bookerly, ale italic variant sa číta hrozne.

Celkovo mám pocit, že keď je italic šikmý, tak sa na e-inku nerenderuje práve najlepšie. Práve z toho dôvodu používam písmo Literata, ktoré má italic takmer rovný, ale z tvaru znakov sa dá ľahko odlíšiť od normálneho písma. Škoda, že vo vstavanej čítačke je prakticky nepoužiteľný kvôli bugom.

PDF

Na Kindli môžem v prípade podpory PDF pochváliť len rýchlosť zoomu. Okrem nastavenia kontrastu nemá Kindle žiadne špeciálne nastavenia pre PDF.

KOReader síce nevie rýchly zoom, má implementovaný reflow a dokonca OCR (tesseract OCR engine). Výber textu a slovníky by mali fungovať aj na naskenovaných dokumentoch.

Knižnica

Pre KOReader je knižnica len obyčajným adresárom. Na rozdiel od Kindle neindexuje na pozadí knihy a nezobrazí všetky naraz.

Nie som náročný na funkcie knižnice, takže to, čo dokáže KOReader mi úplne postačuje. Knižnicu je možné zobrazovať ako zoznam, alebo mriežku s náhľadmi. Zoradenie je možné podľa abecedy, dátumu vytvorenia, dátumu posledného čítania, alebo podľa priebehu čítania. V zozname je možné zobraziť aj sériu kníh a poradové číslo v sérii.

Ovládanie a nastavenia

V čítačke Kindle sa dá nastaviť zobrazenie knižnice (mriežka, zoznam), veľkosť, hrúbka a typ fontu, riadkovanie, zarovnanie a čiastočne intenzita osvetlenia. To je asi všetko, čo sa dá v Kindle nastaviť. Napriek tomu občas ťuknem na nesprávnu ikonku. Ovládanie by mohlo byť vážne intuitívnejšie.

Nastavenia čítačky kindle
Obrázok 6: Nastavenia čítačky kindle

Na prvom obrázku (vľavo hore) je menu settings. Tu nájdeme režim lietadla, okamžitú synchronizáciu (od kedy je okamžitá synchronizácia nastavením?), tlačidlo všetkých nastavení a nastavenie jasu. Na tom istom screenshote je v dolnej časti zobrazená aktuálna kapitola a pod ňou tlačidlá na rýchlu navigáciu v dokumente. Tie tlačidlá vyzerajú ako prepínač zobrazenia, ale v skutočnosti jedno zobrazí popup s prehliadačom stránky a druhé mriežku stránok (v tomto dokumente nedostupné). Z pohľadu UX sú tie 2 tlačidlá absolútna katastrofa, pretože vyzerajú ako prepínač. Ešte horšie je, že keď človek chce rýchlo listovať knihou, musí vliezť do nastavení, ktoré ani nie sú nastaveniami. Nedávno som chcel v jednej knihe nalistovať graf a nepamätal som si presne kde bol a trvalo mi 20(!) minút kým som našiel prehliadač stránok (pamätal som si, že niekde musí byť, ale nevedel som kde).

KOReader má nastavenia rozčlenené do horného menu (vyvolané potiahnutím zhora, alebo kliknutím v hornej časti) a dolného menu (vyvolané potiahnutím zdola, alebo kliknutím). Pred použitím nastavení odporúčam prečítať si príručku.

Predpokladám, že si nikto moju radu aj tak nezobral k srdcu, takže pre istotu spomeniem s čím som mal najväčší problém. KOReader má samostatné nastavenia pre každú knihu. Pri každej knihe si môžem nastaviť vlastnú veľkosť písma, typ fontu, štýly atď. Nastavenia budú platiť len pre danú knihu. Ak v Kindle niečo nastavím, platí to všade. Ak to urobím v KOReaderi, platí to len pre danú knihu.

Okrem nastavení knihy existujú aj defaultné nastavenia. Tie sa prekopírujú do nastavení knihy po prvom otvorení knihy. Ďalšia zmena defaultných nastavení neovplyvní už otvorenú knihu. Aby sa prejavila zmena defaultných nastavení v už otvorenej knihe, je potrebné otvoriť horné menu, kliknúť na ikonu dokumentu a vybrať položku menu Reset document settings to default.

Zostáva už len otázka, ako zmeniť defaultné nastavenia. Tie sa menia dlhým podržaním prsta na voľbe, ktorú chcem nastaviť ako default.

Toto ovládanie nie je intuitívne, pretože bez manuálu užívateľ nemá ako vedieť, že defaultné nastavenia zmení podržaním prsta. Tak isto rozdelenie medzi defaultné nastavenia a nastavenia knihy nie je intuitívne, pretože užívateľ jednoducho čaká, že sa nastavenia prejavia všade.

Že niečo nie je intuitívne ešte neznamená, že je to zlé. Ani Blender nie je intuitívny, ale vďaka takému ovládaniu, aké má je v rukách profesionála extrémne efektívny. Ja musím KOReader preto pochváliť za tieto rozhodnutia, pretože občas narazím na knihu, kde autor nastavil malé písmo, inde naopak príliš veľké a preto sa mi hodí nastaviť veľkosť písma špecificky pre vybranú knihu. Na ovládanie som si zvykol veľmi rýchlo, takže tých 5 minút čítania užívateľskej príručky stálo za to.

KOReader - nastavenia
Obrázok 7: KOReader - nastavenia

Spodné menu je jednoduché, ľahko použiteľné a sprístupňuje rôzne nastavenia zobrazenia dokumentu. Podržaním prsta na názve možnosti sa vyvolá pomoc k danej položke (obrázok vľavo dole). Niektoré nastavenia majú tlačidlo troch bodiek, ktorým sa vyvolá dialóg pre zadanie presnej hodnoty (obrázok hore v strede).

V hornom menu by som chcel upozorniť na položku Taps and gestures. V tejto sekcii je možné priradiť rôzne akcie gestám ako ťahanie prstom pri niektorom okraji, ťahanie dvoma prstami, kliknutie na niektorý z rohov …

Členenie menu nie je ani zďaleka ideálne. Za bežných okolností by som určite skritizoval umiestnenie osvetlenia niekam do horného menu. Našťastie je tu možnosť nastaviť gestá. Momentálne mám nastavenú hodnotu osvetlenia, ktorú používam v noci a akciu zapnutie / vypnutie osvetlenia mám priradenú na ťuknutie vľavo dole. Okrem toho zvýšenie a zníženie jasu mám priradené na potiahnutie 2 prstami, takže nemusím vôbec chodiť do menu.

Pri menu by som sa ešte spomenul katastrofálne zobrazenie položiek (dolný rad, stredný obrázok). Šípky vpravo pri položkách by mali byť zarovnané na pravý okraj, aby zbytočne nerušili text. Čo je horšie je šípka hore v poslednom riadku menu - to je tlačidlo späť.

V spodnej časti je konfigurovateľný status bar. Nastaviť sa dá formát položiek, zoznam položiek, skrývanie ak sú niektoré irelevantné. Dá sa nastaviť napríklad zobrazenie stavu batérie ak je nabitie pod určitou hodnotou, alebo zobraziť ikonu ak je zapnuté osvetlenie (cez deň nemám šancu vidieť, že mám zapnuté osvetlenie, ale ikonu si bez problémov všimnem a vypnem ho keď je zbytočné).

Open source

Zdrojové kódy sú dostupné na githube. Renderovacie jadro je z čítačky Cool Reader. Čítačka sa dá veľmi jednoducho skompilovať a spustiť na desktope (stačí klonovať git clone --recursive https://github.com/koreader/koreader.git a spustiť ./kodev run v adresári koreader).

Užívateľské rozhranie je naprogramované v jazyku lua. Väčšina úprav (prakticky všetko, ak človek nepotrebuje upravovať renderovacie jadro) sa dá robiť bez kompilácie, stačí sa prihlásiť na čítačku cez SSH a priamo upravovať zdrojové kódy. Ak by niekto chcel vyvíjať cez SSH odporúčam:

# štandardná IP adresa
ssh root@192.168.15.244

# zastavenie vstavanej čítačky
stop framework

# spustenie
cd /mnt/us/koreader
./reader.lua

Mnoho funkcií v užívateľskom rozhraní je implementovaných ako zásuvné moduly. Ukážka zásuvného modulu, ktorý sa registruje do menu a dá sa vyvolať aj cez gestá je na githube. Ladiť sa dajú bez problémov na desktope, ako aj na čítačke cez SSH.

Integrácia s Calibre

So vstavanou čítačkou Kindle pracuje calibre bez dodatočných nastavení. Calibre ju automaticky pri pripojení kábla primontuje (je to mass storage) a nastaví príslušný profil v calibre. Štandardne sa dokumenty nahrávajú do adresára documents v dosť šialenej štruktúre autor/kniha.azw3. Adresárová štruktúra však nie je vôbec dôležitá, pretože Kindle zobrazuje všetky knihy v jednom veľkom zozname.

Z funkcií calibre som s Kindlom používal len jednosmerné nahrávanie kníh a konverziu do AZW3. V minulosti som sa pokúšal o synchronizáciu priebehu, ale to sa mi nepodarilo, pretože nikdy som v čítačke nemal tú správnu verziu firmvéru. Nechcelo sa mi s tým vôbec babrať, pretože po aktualizácii môže byť situácia zase iná.

Kindle by sme teda mali, ale ako je na tom KOReader?

Prvým problémom, na ktorý človek narazí je absencia mass storage. Nie je to len nejaká malá drobnosť, ktorá by sa dala vyriešiť, ale je to dôsledok spustenia čítačky z partície s užívateľskými dátami. Pri mass storage musí byť partícia odpojená, čo nie je možné, kým z nej beží KOReader. Nahranie kníh je možné niekoľkými spôsobmi:

Cez mass storage po ukončení KOReadera
Táto možnosť by bola celkom akceptovateľná, keby vstavaná čítačka neštartovala 2-3 minúty (mimochodom KOReader sa dá spustiť aj bez ukončenia behu vstavanej čítačky, vtedy zariadeniu síce zostane menej RAM, ale človek nemusí čakať).
Cez wifi
Zapínať wifi na jailbreaknutom Kindle vážne nie je dobrý nápad. Amazon sa snaží všetkými prostriedkami bojovať proti jailbreaku a neexistuje spoľahlivý spôsob ako zablokovať aktualizácie.
Cez ssh
Osobne uprednostňujem možnosť nechať permanentne zapnutý prístup cez ssh a nahrávať knihy po pripojení cez sshfs.

Z dostupných možností som si vybral podľa mňa najmenej zlú - ssh.

Pripojenie súborového systému

Aby bolo možné použiť ssh, musí sa Kindle najskôr prepnúť do režimu USB sieťovky. Na to slúži USBNetwork hack.

Balík sa najskôr musí rozbaliť, následne sa jeden z .bin súborov (podľa podľa zariadenia) umiestní do adresára mrpackages a nakoniec sa nainštaluje cez menu KUAL pomocou položky Install MR Packages.

Po reštarte odporúčam ešte raz spustiť KUAL a v menu USBNetwork povoliť spustenie SSH po štarte.

Teraz sa bude Kindle tváriť ako sieťová karta obsluhovaná modulom jadra cdc_ether. Keďže väčšina distribúcii používa takzvané "predictable names", bude názov sieťovky nejaká mňamka ako enp7s0f4u2i1. Aby nebolo málo zábavy, tento názov používa aj moja dokovacia stanica, takže reálne môže dostať aj nejakú inú mňamku.

Pre krátky test môžem nastaviť IP adresu a skúsiť, či SSH funguje korektne:

ip link set enp7s0f4u2i1 up; ip addr add 192.168.15.243/24 dev enp7s0f4u2i1

Štandardná adresa, ktorú má kindle je 192.168.15.244, takže prihlásiť sa môžem príkazom (heslo je prázdny reťazec):

$ ssh root@192.168.15.244


Welcome to Kindle!

root@192.168.15.244's password:
#################################################
#  N O T I C E  *  N O T I C E  *  N O T I C E  #
#################################################
Rootfs is mounted read-only. Invoke mntroot rw to
switch back to a writable rootfs.
#################################################
[root@kindle root]#

Pripojenie by fungovalo, poďme teraz automatizovať pripojenie súborového systému. Začnem zistením informácií o sieťovom rozhraní zo subsystému udev:

udevadm info -p /sys/class/net/enp7s0f4u2i1

P: /devices/pci0000:00/0000:00:08.1/0000:07:00.4/usb5/5-2/5-2:1.0/net/enp7s0f4u2i1
L: 0
E: DEVPATH=/devices/pci0000:00/0000:00:08.1/0000:07:00.4/usb5/5-2/5-2:1.0/net/enp7s0f4u2i1
E: INTERFACE=enp7s0f4u2i1
E: IFINDEX=6
E: SUBSYSTEM=net
E: USEC_INITIALIZED=18056489502
E: ID_NET_NAMING_SCHEME=v249
E: ID_NET_NAME_MAC=enxee4900000000
E: ID_NET_NAME_PATH=enp7s0f4u2
E: ID_VENDOR=Linux_3.0.35-lab126_with_fsl-usb2-udc
E: ID_VENDOR_ENC=Linux\x203.0.35-lab126\x20with\x20fsl-usb2-udc
E: ID_VENDOR_ID=0525
E: ID_MODEL=RNDIS_Ethernet_Gadget
E: ID_MODEL_ENC=RNDIS\x2fEthernet\x20Gadget
E: ID_MODEL_ID=a4a2
E: ID_REVISION=0399
E: ID_SERIAL=Linux_3.0.35-lab126_with_fsl-usb2-udc_RNDIS_Ethernet_Gadget
E: ID_TYPE=generic
E: ID_BUS=usb
E: ID_USB_INTERFACES=:0202ff:0a0000:020600:
E: ID_USB_INTERFACE_NUM=00
E: ID_USB_DRIVER=cdc_ether
E: ID_USB_CLASS_FROM_DATABASE=Communications
E: ID_VENDOR_FROM_DATABASE=Netchip Technology, Inc.
E: ID_MODEL_FROM_DATABASE=Linux-USB Ethernet/RNDIS Gadget
E: ID_MM_CANDIDATE=1
E: ID_PATH=pci-0000:07:00.4-usb-0:2:1.0
E: ID_PATH_TAG=pci-0000_07_00_4-usb-0_2_1_0
E: ID_NET_DRIVER=cdc_ether
E: ID_NET_LINK_FILE=/lib/systemd/network/99-default.link
E: ID_NET_NAME=enp7s0f4u2i1

Zaujímavé položky sú napríklad ID_VENDOR, alebo ID_NET_NAME_MAC. Keďže sú názvy nespoľahlivé, začnem premenovaním zariadenia na niečo stabilné pomocou MAC adresy (dal by sa zneužiť aj výrobca). Vytvorím si, alebo pridám do súboru /etc/udev/rules.d/98-network.rules nasledujúci riadok:

SUBSYSTEM=="net", ACTION=="add", ENV{ID_NET_NAME_MAC}=="enxee4900000000", NAME="eth_kindle"

Toto pravidlo nastaví názov sieťového zariadenia na eth_kindle. Teraz nasleduje pomerne hnusný hack, ktorý si môžem dovoliť, pretože môj počítač používam sám.

Pod rootom teda vytvorím adresár /mnt/kindle príkazom mkdir -p /mnt/kindle a priradím mu práva chown uzivatel:skupina /mnt/kindle, v mojom prípade chown mirec:mirec /mnt/kindle.

Do systému nainštalujem balíky sshfs a sshpass. Ďalej nahradím pôvodný riadok v /etc/udev/rules.d/98-network.rules novými riadkami pre pripojenie a odpojenie (pribudol len príkaz RUN).

SUBSYSTEM=="net", ACTION=="add", ENV{ID_NET_NAME_MAC}=="enxee4900000000", NAME="eth_kindle" RUN+="/etc/udev/scripts/kindle_start"
SUBSYSTEM=="net", ACTION=="remove", ENV{ID_NET_NAME_MAC}=="enxee4900000000", NAME="eth_kindle" RUN+="/etc/udev/scripts/kindle_stop"

Skript pre odpojenie /etc/udev/scripts/kindle_stop bude veľmi jednoduchý:

#!/bin/bash
fusermount -z -u /mnt/kindle

Samozrejme skript musí mať príkaz spustiteľný (chmod +x /etc/udev/scripts/kindle_stop). To isté bude platiť aj pre skript kindle_start, ktorý vyzerá takto:

#!/bin/bash
ip link set eth_kindle up
ip addr add 192.168.15.243/24 dev eth_kindle
su uzivatel -c "sshfs root@192.168.15.244:/mnt/us/ /mnt/kindle"
echo "date -s \"`date +'%Y-%m-%d %H:%M:%S'`\";hwclock -w -u"|sshpass -p "" ssh -o StrictHostKeyChecking=no root@192.168.15.244

Nastavenie adresy nemusím vysvetľovať. Pripojenie sa vykonáva pod bežným užívateľom cez fuse. Čo tam však robí date?

Jailbreaknutý Kindle používam permanentne v režime lietadla. V takom prípade nie je možné synchronizovať čas. Presnosť hodín reálneho času na mojej čítačke je nič moc, okolo 30s chyba mesačne. Nie je to veľa, ale je to dosť na to, aby sa mi nechcelo nastavovať čas. To však nevysvetľuje dôvod, prečo som nepoužil NTP.

Časová zóna /etc/localtime je odkaz na /var/local/system/tz, čo je nejaký súbor generovaný Amazonom. Koreňový adresár zostáva permanentne read-only. Príkaz date vypíše toto:

# date
Sat May 14 09:12:30 GMT+2:22100 2022

Čo je GMT+2:22100? Prečo je časový posun oproti GMT 8520 sekúnd (2 hodiny a 22 minút)? Nastavenie času cez ntp mi posunie hodiny o 22 minút. Mohol by som teoreticky nahradiť súbor časovej zóny, ale pri ďalšom štarte by sa to zase rozbilo.

Správa kníh cez Calibre

Na začiatok je potrebné spustiť welcome wizard. Pre Kindle sa totiž štandardne používajú formáty AZW3 a MOBI. Teoreticky sa Calibre tvári, že je možné ich zmeniť v nastaveniach, ale stále existujú automatické konverzie, ktoré mení len wizard. Bez tohto kroku by síce boli knihy správne odosielané v EPUB-e, ale správy by sa napríklad stále ukladali do MOBI.

Spustenie welcome wizardu
Obrázok 8: Spustenie welcome wizardu
Nastavenie zariadenia
Obrázok 9: Nastavenie zariadenia

Čítačka sa dá pridať cez tlačidlo pripojenia a výber adresára /mnt/kindle/documents/.

Pripojenie čítačky v calibre
Obrázok 10: Pripojenie čítačky v calibre

V nastaveniach adresára je potrebné ešte nastaviť podporované formáty, pretože pri zvolení generického zariadenia bude akceptovať aj formáty, ktoré nepodporuje. Taktiež odporúčam zmeniť šablónu (pattern, ktorý sa používa pre uladanie), pretože štandardne ukladá v adresárovej štruktúre Autor / Kniha, čo sa mi na čítačke nepoužíva práve najpríjemnejšie.

Menu adresára
Obrázok 11: Menu adresára
Nastavenie adresára
Obrázok 12: Nastavenie adresára

KOReader sync

Dodatočný zásuvný modul, KOReader sync slúži na načítanie niektorých atribútov z čítačky. Nastavenie nie je úplne najintuitívnejšie, preto skúsim vysvetliť ako funguje napríklad taká synchronizácia priebehu čítania.

Najskôr je potrebné vytvoriť si stĺpec, do ktorého sa bude údaj načítavať. V tomto prípade to bude desatinné číslo formátované (python formátovanie) ako percentuálna hodnota.

Pridanie stĺpca
Obrázok 13: Pridanie stĺpca

V nastaveniach KOReader sync už stačí len spárovať import so stĺpcom a zobraziť stĺpec progress v zozname kníh. Po stlačení tlačidla synchronizácie sa načíta stav kníh zo zariadenia.

Pridadenie stĺpca
Obrázok 14: Priradenie stĺpca

Spustenie bez framweorku

KOReader môže na zariadeniach Amazon Kindle naraziť na nedostatok operačnej pamäte. Pri náročnejších PDF súboroch zvykne štandardná čítačka padať na nedostatku pamäte. Zvyčajne na chvíľu pomôže reštart. KOReader má rovnaký problém, aj keď sám veľa operačnej pamäte nepotrebuje.

Pamäť
Obrázok 15: Pamäť

Pre pochopenie nasledujúcej časti bude potrebné poznať niektoré komponenty. Začnem preto rovno malým slovníkom.

Framework
GUI rozhranie od amazonu (napísané v jave)
cvm
Hlavný proces GUI
KAF
Kindle amazon framework?
awesome
Desktopové prostredie, skriptovateľné v LUA
lipc
IPC protokol využívajúci D-Bus, podmnožina D-Bus, existuje open source implementácia
powerd
Daemon starajúci sa o napájanie
blanket
Proces starajúci sa o zobrazenie bootovacej obrazovky, screensavera, priebehu aktualizácie … Rôzne moduly sú implementované ako pluginy a existuje open source implementácia screensavera.
upstart
Init systém vytvorený spoločnosťou Canonical
LibElektra
Konfiguračné registry v stromovej štruktúre (niečo ako registry vo windowse)
kdb
Nástroj na prácu s LibElektra registrami.

Výpis htop vyzerá ako z viac-menej normálneho linuxového desktopu. Nájdeme tu paradoxne X.org server, čo je celkom zaujímavá voľba. Jednouchšie embedded systémy používajú väčšinou priamy prístup do framebufferu. Výkonnejšie zariadenia zase zvyknú používať kompozitor s podporou OpenGL ES. Prečo je použitý X.org?

Jednou z možných príčin je absencia grafickej akcelerácie na použitom SoC (Freescale i.MX 6). Druhou je nenažranosť GPU. Nedávno som sa hral so spotrebou môjho thinkpadu a prekvapujúco najnižšiu spotrebu som dosahoval pri spustenom X.org bez aplikácií využívajúcich grafickú akceleráciu. Ak som používal napríklad akcelerovaný emulátor terminálu Alacritty namiesto rxvt-unicode, spotreba bola o nejakú tú desatinu watu vyššia. Pri zapnutom waylande to už bolo viac o 0.3 W (prostredie sway).

Spustenie z konzoly

GUI sa štartuje skriptom /etc/upstart/lab126_gui. Zastaviť sa dá jednoducho príkazom stop lab126_gui.

Teraz stačí prejsť do adresára /mnt/us/koreader a spustiť lua skript reader.lua:

cd /mnt/us/koreader
./reader.lua

Výsledok vyzerá dobre. Čítačka funguje a pamäť je poloprázdna.

Pamäť bez frameworku
Obrázok 16: Pamäť bez frameworku

Spustenie z KUAL menu

Skript pre spustenie KOReadera implementuje ukončenie frameworku. Stačí povoliť položku v KUAL menu odstránením podmienky v extensions/koreader/menu.json.

Všetko okrem tlačidla power funguje bez problémov. V nasledujúcom texte sa budem venovať len oprave obsluhy tlačidla power.

Oprava power - lamer štýl

Tento spôsob považujem za slepú uličku, ale možno niekoho zaujme.

Situácia je taká, že nereaguje na tlačidlo power. Na začiatok je dobré zistiť, či sa udalosť generuje cez štandardný linuxový input subsystém. Aby som si to overil, skúsim najväčšiu blbosť: cat /dev/input/event0. Stlačím power a vypadne na mňa binárny humus. Tlačidlo teda mám a je pripojené na event0.

Do kódu inicializácie zariadenia pridávam:

self.input.open("/dev/input/event0")

Do handleKeyBoardEv pridávam:

print(ev.code)

Po spustení ./reader.lua a stlačení tlačidla vidím v konzole číslo 116. Ďalej do kódu inicializácie pridávam mapovanie udalosti 116 s internou udalosťou Power.

self.input = require("device/input"):new{
    device = self,
    event_map = {
        [116] = "Power",
    },
}

Interne sa táto udalosť konvertuje na PowerPress a PowerRelease.

Zostáva už len zaregistrovať udalosti do kódu isKindle a filtrovať udalosti ak je spustený screensaver (keďže teraz píšeme len softvérovú podporu, zostáva dotyková vrstva stále aktívna, bez filtrovania by sa dotykom vypol screensaver):

self.event_handlers["PowerRelease"] = function()
    if not self._entered_poweroff_stage then
        UIManager:unschedule(self.poweroff_action)
        -- resume if we were suspended
        if Device.screen_saver_mode then
            Device:outofScreenSaver()
            self:_afterResume()
        else
            self:_beforeSuspend()
            Device:intoScreenSaver()
            Device:suspend()
        end
    end
end
self.event_handlers["__default__"] = function(input_event)
    if not Device.screen_saver_mode then
        self:sendEvent(input_event)
    end
end

Po štarte KOReadera nabehne po stlačení tlačidlá power screensaver. Funkcionalita je viac-menej hotová, akurát sa čítačka nehibernuje. Nie je to úplne ideálne riešenie, ale na zablokovanie displaya a občasné utretie prachu postačí ;)

Oprava power - normálne riešenie

Najskôr začnem simuláciou udalostí. Utilita powerd_test slúži na simuláciu power tlačidla.

powerd_test
powerd_test: Utility to test powerd
Options:
  s          : Print status
  i          : Simulate input event
  p          : Simulate power button pressed event
  h          : Simulate power button held event
  l          : Simulate low batt low event
  d <value>  : Set defer sleep property to given value
  r          : Simulate a random series of events at random intervals.
               Max interval is 10s by default.
  b<optional>: Set fake battery level if a value is provided. Remember to
               there is no space between b and value.  If no value is
               provided, it interactively accepts values from you and sets
               fake battery level
  t <value>  : Max interval between events in seconds.
               Valid only when used with -r
  NOTE       : Only one of i,p,d, b and r can be used at one time
</value></optional></value>

Príkaz powerd_test -p so zapnutým frameworkom uzamkne obrazovku, ale bez frameworku nerobí nič. Keďže nie sú dostupné zdrojové kódy, pozriem sa programu na zúbok pomocou dekompilátora ghidra.

powerd_test
Obrázok 17: powerd_test

Super nástroj je Search / Strings. Po vyhľadané výsledkov power mi udrelo do očí dbg_power_button_pressed. Po dvojkliku na referenciu som sa dostal k funkcii na screenshote.

Utilitka podľa všetkého spúšťa príkaz lipc-send-event com.lab126.powerd.debug dbg_power_button_pressed. Príkaz prebehne bez chyby, takže problém bude asi niekde inde. Pokračujem dekompiláciou programu powerd.

V ňom si všímam reťazce začínajúce sa na dbg_. Vidím tu zaujímavé reťazce dbg_mag_sensor_opened a dbg_mag_sensor_closed. Po spustení lipc-send-event com.lab126.powerd.debug dbg_mag_sensor_closed sa spustí screensaver a stav zariadenia sa zmení na Screen Saver:

powerd_test -s
Powerd state: Screen Saver
Remaining time in this state: Unknown
defer_suspend:0
suspend_grace:0
prevent_screen_saver:0
drive_mode_state:1
PRIMARY BATTERY
Battery Level: 100%
Last batt event at: 100%
Charging: Yes
Battery logging: On

Po spustení lipc-send-event com.lab126.powerd.debug dbg_mag_sensor_opened je zariadenie opäť v aktívnom stave:

powerd_test -s
Powerd state: Active
Remaining time in this state: 598.386396
…

Chyba bude niekde v spracovaní udalosti tlačidla. Podľa reťazcov nachádzam v dekompilátore nasledujúci kód:

iVar2 = LipcRegisterIntProperty(DAT_0003957c,"powerButton",0,&LAB_000145f0,0);

Existenciu property potvrdzuje aj výpis lipc-probe:

lipc-probe com.lab126.powerd
        w       Int     touchScreenSaverTimeout
        w       Int     deferSuspend
        w       Int     wakeUp
        r       Int     flRawIntensity
        w       Int     rtcWakeup
        r       Str     state
        w       Int     suspendGrace
        r       Str     battStateInfo
        w       Int     deferRestart
        w       Str     shutdownAlertResponse
        r       Int     battTemperature
        w       Int     addSuspendLevels
        rw      Str     logLevel
        r       Int     battLevel
        rw      Int     preventScreenSaver
        rw      Str     logMask
        w       Int     sendPendingResume
        rw      Int     flAuto
        rw      Int     flIntensity
        r       Int     flMaxIntensity
        w       Str     rtcWakeup2
        w       Int     abortSuspend
        r       Int     flTurboBrightnessIntensity
        w       Int     powerButton
        w       Int     flStartup
        r       Str     status
        r       Int     isLowTempState
        r       Int     isCharging

Volanie lipc-set-prop com.lab126.powerd powerButton 1 však nerobí nič. V zásade som neprišiel na nič zaujímavé okrem toho, že kód obsahuje množstvo ladiacich výpisov. V property však vidím logMask. Nastavujem preto lipc-set-prop com.lab126.powerd logMask "0xffff" a pozerám, čo sa deje v /var/log/messages. Nastavenie property powerButton vypíše nasledujúci kód:

220514:194019 powerd[5962]: E lipc:dbuserr:name=org.freedesktop.DBus.Error.ServiceUnknown:Receive Dbus error message (DBUS_MESSAGE_TYPE_ERROR): The name com.lab126.kaf was not provided by any .service files
220514:194019 powerd[5962]: D lipc:axstime:prop=frameworkStarted, source=com.lab126.kaf, ms=4:lipc property access time in milliseconds (PrvAccessPropertySync:access_properties.c:702)
220514:194019 powerd[5962]: I def:pbisplash::Splash screen is on.  Ignoring power button

Výpis relevantnej časti kódu z dekompilátora vyzerá nasledovne:

iVar2 = FUN_000167bc();
if (iVar2 != 0) {
  if (DAT_000395ec != (undefined **)0x0) {
    FUN_0001f0bc(DAT_000395ec);
    DAT_00038ac4 = 0xffffffff;
    DAT_000395ec = (undefined **)0x0;
    return 0;
  }
  pcVar4 = "active_handle_power_button";
  pcVar3 = "PowerButtonPress";
  goto LAB_0001ce18;
}
if ((DAT_00039450 & 0x800000) == 0) {
  return 0;
}
iVar2 = 1;
pcVar4 = "I def:pbisplash::Splash screen is on.  Ignoring power button";

Ak je iVar2 0, potom je vraj zobrazený screen saver. Zostáva preskúmať funkciu FUN_000167bc.

powerd
Obrázok 18: powerd

Kód najskôr zisťuje hodnoty registrov system/daemon/powerd/KAF_NAME a system/daemon/powerd/KAF_FRAMEWORK_STARTED. Pozrime sa na tieto hodnoty pomocou nástroja kdb:

kdb get system/daemon/powerd/KAF_NAME
com.lab126.kaf
kdb get system/daemon/powerd/KAF_FRAMEWORK_STARTED
frameworkStarted

Ďalej sa volá lipc. Volanie je ekvivalentné príkazu:

lipc-get-prop com.lab126.kaf frameworkStarted
com.lab126.kaf failed to access property frameworkStarted (0x3 lipcErrNoSuchSource)

Funkcia považuje za zobrazený splash screen ak framework nehlási vlastnosť frameworkStarted. Riešením je spustiť proces, ktorý sa bude odpovedať na property frameworkStarted. Vytvorím si jednoduchý súbor test.lua a spustím ho (lua test.lua):

require("liblipclua")

lipc_handle, error_message, error_number = lipc.init("com.lab126.kaf")

local readonly_property = lipc_handle:register_int_property("frameworkStarted", "r")
readonly_property.value = 1

while true do lipc.run_event_loop(math.huge) end

Po spustení sa však nič nezmenilo. Do pozornosti dávam vo výpise riadok return 0. Podľa dekompilátora funkcia vráti vždy 0 bez ohľadu na predchádzajúce volania. Dekompilácia optimalizovaného C kódu je zložitá. Výsledný kód nie je vždy korektný a obyčajne sa dá spoznať podľa nejakej blbosti, napríklad funkcie, ktorá vracia vždy konštantnú hodnotu. Toto je presne ten prípad. V disassembleri je však korektne viditeľné pokračovanie funkcie, ktoré načítava registre system/daemon/powerd/BLANKET_NAME a system/daemon/powerd/BLANKET_LOAD.

kdb get system/daemon/powerd/BLANKET_NAME
com.lab126.blanket
kdb get system/daemon/powerd/BLANKET_LOAD
load
lipc-get-prop com.lab126.blanket load
langpicker blankwindow usb splash

Ďalej sa testuje, či property load služby com.lab126.blanket obsahuje text splash. Služba blanket má zaujímavé property ako load a unload, pomocou ktorých sa dajú načítať, alebo odobrať pluginy:

lipc-probe com.lab126.blanket
        rw      Str     unload
        rw      Str     logMask
        rw      Str     load
        w       Str     uiQuery
        rw      Str     logLevel

Po odobratí splash (lipc-set-prop com.lab126.blanket unload splash) začne fungovať uspávanie tlačidlom power.

Z tohto kódu som vytvoril plugin pre KOReader, ktorý stačí uploadnúť do adresára koreader/plugins a reštartovať KOReader.

Teraz funguje všetko korektne vrátane obsluhy tlačidla power.

Bonus tip - kterm

Pri hraní sa s KOReaderom som napísal ešte jeden malý plugin, ktorý umožňuje naštartovať emulátor terminálu Kterm.

Spustiť emulátor terminálu pod KOReaderom nie je tak jednoduché, ako by sa mohlo zdať, pretože KOReader nepoužíva X11, ale vykresľuje grafiku priamo na framebuffer. Po štarte terminálu sa musí zastaviť vykresľovanie, čo zase nie je až taký problém. Spracovanie udalosti zasekne GUI kým sa nedokončí spracovanie, takže stačí spustiť KOReader na popredí.

Väčším problémom je, že KOReader blokuje vstup. Okno terminálu síce nabehne, ale nebude mať prijímať žiadne udalosti dotyku. Problém som vyriešil jednoducho odpojením vstupných vstupov pred štartom terminálu a obnovením vstupov po štarte. Kód nakoniec nie je vôbec zložitý.

Nakoniec ešte ukážkové video:

Kterm

Boot priamo do KOReaderu

Kindle bootuje veľmi dlho. Samotný systém do X.org serveru dokáže naštartovať za 10s, ale vstavaná aplikácia čítačky štartuje 2-3 minúty. Teoreticky by bolo možné deaktivovať službu gui cez upstart a štartovať priamo KOReader, vďaka čomu by bola čítačka použiteľná za asi 15s po reštarte. Reálne sú tu rôzne kontroly, či Kindle nabootoval a v prípade 3 neúspešných pokusov sa zasekne na obrazovke oznamujúcej nutnosť opravy. Pravdu povediac neviem, či v tomto režime nabehne vôbec SSH daemon a nechcem riskovať, že si úplne zablokujem prístup. Keby napriek tomu niekto chcel experimentovať, odporúčam pozrieť toto vlákno, kde je ukážka úpravy upstart skriptu.

Takže milé deti, neskúšajte to doma (aspoň nie, ak nemáte pripravený UART a nástroje na rozobratie).

    • RE: KOReader na čítačke Amazon Kindle - inštalácia, skúsenosti 14.05 | 23:08
      Avatar bluesundown   Používateľ

      dakujem za fajn clanok. KOReader som pouzival na citacke, Literatu som si nahodil na tvoju radu. Je to celkom fajn.

      KOReader ma jednu fajn ficurku - perception expander ktory v kombinacii s nastavenim okrajov moze priniest lepsi citatelsky zazitok.