Distribuer votre appli XULRunner
4 - Mac OSX
- 4.1 Icônes pour Mac OSX
- 4.2 Créer un lanceur
- 4.3 Quelques spécifités Mac
- 4.4 Créer un "Application Bundle"
- 4.5 Empaqueter un tar.gz et un dmg
4.1 Icônes pour Mac OSX
Nous avons besoin d'une icône icns Sur Mac les fenêtres n'ont pas d'icône, mais, plus tard, nous en utiliserons une dans le Dock. Et elle sera utilisée pour le bundle que nous créerons.
Nous pouvons générer ce fichier .icns
directement depuis
Linux. La restriction est que nous ne pouvons pas inclure des images avec
pluieurs profondeur de couleur, mais des dimensions multiples est possible.
Nous avons besoin du programme png2icns
compris dans le
paquet icnsutils.
Sur Debian/Ubuntu:
apt_get install icnsutils
Nous obtenoons notre fichier icns à partir de plusieurs png avec :
png2icns icon.icns icon128.png icon48.png icon32.png icon16.png
Vous trouverez un script bash avec cette ligne de commande dans l'archive
d'exemple relative à ce chapitre 4
(dans samples_chapter_4/data/icons/
).
4.2 Créer un lanceur
Comme pour Linux, nous utiliserons un script shell pour notre lanceur. Mais nous avons plusieurs problèmes à résoudre.
le premier n'est pas trop difficile. Le programme readlink
disponible sur Mac OSX n'est pas la version GNU, et nous ne pouvons pas
utiliser l'option -f
. Nous résouderons nous même cette fonctionnalité.
Un problème plus sérieux, est que nous ne pouvons pas déterminer où
Firefox est installé. Donc, ici, nous imposerons une limitation,
Firefox doit être installer à son emplacement par défaut,
c'est à dire /Applications/
.
Note : si vous une suggestions pour déterminer où est installer Firefox, merci de me le faire savoir ;) .
Voici ce script, myapp-mac.sh
, bien que ce ne soit
pas exactement celui que nous utiliserons plus tard :
#!/bin/sh set -e # see http://stackoverflow.com/questions/7665/how-to-resolve-symbolic-links-in-a-shell-script # get the absolute path of the executable SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && \ pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0") # resolve symlinks while [ -h $SELF_PATH ]; do DIR=$(dirname -- "$SELF_PATH") SYM=$(readlink $SELF_PATH) SELF_PATH=$(cd $DIR && cd $(dirname -- "$SYM") && pwd)/$(basename -- "$SYM") done CUR_DIR=$(dirname "$SELF_PATH") if [ -x /Applications/Firefox.app/Contents/MacOS/firefox-bin ]; then /Applications/Firefox.app/Contents/MacOS/firefox-bin -app "$CUR_DIR/application.ini" $@ elif [ -x /Library/Frameworks/XUL.framework/xulrunner-bin ]; then /Library/Frameworks/XUL.framework/xulrunner-bin "$CUR_DIR/application.ini" $@ else echo "Error: unable to find Firefox or XULRunner!" fi
Si nous essayons ce lanceur, nous pouvons voir 2 autres problèmes,
l'icône affichée dans le dock est celui de Firefox, et le menu principal,
quand nous fermons toutes les fenêtres de notre appli, est celui de Firefox.
il y a des solutions à ces problèmes, comme nous le verrons dans les prochaines
parties.
Notez que si vous voulez vraiment utiliser un tel script, sans un bundle,
vous pouvez vouloir l'utiliser avec l'extension .command
plutôt que .sh
, car alors le fichier peut être double-cliqué
dans le Finder de Mac, ce qui ouvrira un terminal, puis l'appli.
4.3 Quelques spécifités Mac
Une particularité sur Mac, est le Menu principal contrôler par le système d'exploitation, et correspondant à l'application courante qui a le focus. Et quand toutes les fenêtres d'une même application sont fermées, ce menu reste présent tant que l'on ne quitte pas l'application;
Ceci est bien pris en charge dans une application XULRunner, mais nous devons ajouter un peu de code pour celà.
Quelques docuementation sur MDN:
Si notre appli utilises un élément menubar
principal, XULRunner/Firefox
l'utilisera (en fait le premier menubar) pour créer ce menu spécial. Si vous n'en
avez pas, vous devriez en créer un, masquer par défaut pour les autres plateformes,
il permettra la construction de ce menu.
les entrées dans le menu "pomme" sont construits d'après des éléments avec
des id
réservés. Vous devriez créer au moins quelques entrées,
pour permettre de quitter l'appli par exemple.
Voici la liste des id disponibles correspondants à ces entrées (sources) :
id de l'élément | item du menu correspondant |
---|---|
aboutName |
À propos de cette application |
menu_preferences |
Préférences... |
menu_mac_services |
Services |
menu_mac_hide_app |
Masquer l'appli |
menu_mac_hide_others |
Masquer les autres |
menu_mac_show_all |
Tout afficher |
menu_FileQuitItem |
Quitter |
et voici un exemple d'un menu xul minimal pour cet usage :
<commandset id="main-commands"> <command id="cmd:quit" oncommand="myappQuitApplication();"/> </commandset> <keyset id="ui-keys"> <key id="key:quitApp" key="Q" modifiers="accel" command="cmd:quit"/> <key id="key:hideApp" key="H" modifiers="accel"/> <key id="key:hideOthersApp" key="H" modifiers="accel,alt"/> </keyset> <menubar id="main-menubar" hidden="true"> <menu id="mac-menu"> <menupopup> <menuitem id="menu_mac_hide_app" label="Hide My App" key="key:hideApp"/> <menuitem id="menu_mac_hide_others" label="Hide Others" key="key:hideOthersApp"/> <menuitem id="menu_mac_show_all" label="Show All"/> <menuitem id="menu_FileQuitItem" label="Quit" key="key:quitApp" command="cmd:quit"/> </menupopup> </menu> </menubar>
Bien sur, comme pour les autres parties de l'appli, les chaînes utilisées dans les labels et clés devraient être localisées. Mais ce n'est pas le propos de ce tutoriel.
La fonction myappQuitApplication
a été ajoutée dans le fichier
main.js
de notre appli :
function myappQuitApplication() { const Cc = Components.classes; const Ci = Components.interfaces; let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. getService(Ci.nsIAppStartup); appStartup.quit(Ci.nsIAppStartup.eAttemptQuit); return true; }
Enfin, le dernier problème : quand toutes les fenêtres de notre
appli sont fermées, en fait l'appli ne quitte pas, et le menu principal
demeure. Plus, tel quel, c'est le menu de Firefox que nous voyons.
En fait, il y a une fenêtre speciale et cachée que XULRunner utilises pour ce menu.
Donc nous devons créer une telle fenêtre, et définir une préférence pour
la spécifier.
Voilà la préférence ajoutée dans defaults/preferences/pref.js
:
pref("browser.hiddenWindowChromeURL", "chrome://myapp/content/hiddenWindow.xul");
Et le contenu de hiddenWindow.xul
, qui contient uniquement notre menubar :
<window id="hiddenWindow" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script type="application/javascript"> <![CDATA[ function myappQuitApplication() { const Cc = Components.classes; const Ci = Components.interfaces; let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. getService(Ci.nsIAppStartup); appStartup.quit(Ci.nsIAppStartup.eAttemptQuit); return true; } ]]> </script> <commandset id="main-commands"> <command id="cmd:quit" oncommand="myappQuitApplication();"/> </commandset> <keyset id="ui-keys"> <key id="key:quitApp" key="Q" modifiers="accel" command="cmd:quit"/> <key id="key:hideApp" key="H" modifiers="accel"/> <key id="key:hideOthersApp" key="H" modifiers="accel,alt"/> </keyset> <menubar id="main-menubar" hidden="true"> <menu id="mac-menu"> <menupopup> <menuitem id="menu_mac_hide_app" label="Hide My App" key="key:hideApp"/> <menuitem id="menu_mac_hide_others" label="Hide Others" key="key:hideOthersApp"/> <menuitem id="menu_mac_show_all" label="Show All"/> <menuitem id="menu_FileQuitItem" label="Quit" key="key:quitApp" command="cmd:quit"/> </menupopup> </menu> </menubar> </window>
4.4 Créer un "Application Bundle"
Sur Mac OSX, les applications sont empaquetées dans un format spécial
nommé Application Bundle. C'est en fait un dossier, nommé
avec l'extension .app
, et avec une structure particulière.
Quelques documentations utiles :
- Inside Application Bundles
- Runtime Configuration Guidelines: Introduction
- Custom app bundles for Mac OS X
Voici une proposition d'un dossier .app
pour notre appli :
|- MyApp.app/ |- Contents/ |- MacOS/ |- chrome/ |- defaults/ |- application.ini |- chrome.manifest |- foxstub |- myapp-mac.sh |- Resources/ |- myapp.icns |- Info.plist |- PkgInfo
le dossier MacOS
contient en fait tous les fichiers
et dossiers de notre appli XULRunner.
le dossier Resources
contient uniquement notre
icône au format icns.
Le fichier PkgInfo
contient uniquement la chaîne
APPL????
. Je ne suis pas sûr que ce fichier soit réellement nécessaire,
il semble important pour la compatibilité avec Mac OS 9.
La valeur spécifie que ce bundle est une application, et comme nous n'avons
pas d'identifiant de 4 caractères valide, nous utilisons 4 points d'interrogation
(????
).
Le fichier Info.plist
décrit notre appli :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>myapp-mac.sh</string> <key>CFBundleGetInfoString</key> <string>MyApp 1.0</string> <key>CFBundleIconFile</key> <string>myapp</string> <key>CFBundleIdentifier</key> <string>net.yourcompany.myapp</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>MyApp</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>1.0.0.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1.0.0.0</string> <key>NSAppleScriptEnabled</key> <true/> </dict> </plist>
Quelques commentaires sur ces propriétés :
CFBundleExecutable
pointe sur notre lanceur,
le script shell nommé myapp-mac.sh
.
la valeur de CFBundleIconFile
est
myapp
parce que notre icône dans le dossier Resources est nommée
myapp.icns
. Maintenant, l'utilisateur final
visualise notre bundle avec notre icône.
CFBundleIdentifier
est formaté tel que
domaine inversé point nom de l'application
. Ce doit être un
identifiant unique pour notre appli.
CFBundlePackageType
spécifie que c'est un
"application bundle" (APPL
).
La valeur de CFBundleSignature
est
????
parce que nous n'avons pas de signature. C'est supposé
être un identifiant unique de 4 caractères, et supposé être obtenue
après un enregistrement auprès d'Apple. Tel que je le comprend, c'est
exiger essentiellement pour Mac OS 9. Ne vous inquiétez pas, je n'ai pas
vu de problème avec cette valeur ;) .
Maintenant voyons une belle astuce. Tel quel, l'utilisateur
final voit notre appli avec notre icône, mais quand il la lance, l'icône
dans le dock est celle de Firefox.
Dans l'arborescence du dossier de notre bundle, vous pouvez voir un fichier
nommé foxstub
, ce fichier est en fait un
lien symbolique vers l'exécutable de Firefox.
Ce lien est obtenu simplement, dans le dossier MacOS, avec :
ln -s /Applications/Firefox.app/Contents/MacOS/firefox-bin foxstub
Maintenant quand l'utilisateur lance notre appli, c'est bien notre icône qui est affichée dans le dock :) .
En fait, notre icône apparait dans le dock, puis disparait, et réapparait de nouveau, parce que notre script est lancé, puis l'exécutable lié. mais ce n'est pas un gros problème.
Voici le script modifié de notre lanceur myapp-mac.sh
, qui
utilise dorénavant le lien symbolique vers Firefox :
#!/bin/sh set -e # see http://stackoverflow.com/questions/7665/how-to-resolve-symbolic-links-in-a-shell-script # get the absolute path of the executable SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && \ pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0") # resolve symlinks while [ -h $SELF_PATH ]; do DIR=$(dirname -- "$SELF_PATH") SYM=$(readlink $SELF_PATH) SELF_PATH=$(cd $DIR && cd $(dirname -- "$SYM") && pwd)/$(basename -- "$SYM") done CUR_DIR=$(dirname "$SELF_PATH") if [ -x "$CUR_DIR/foxstub" ]; then "$CUR_DIR/foxstub" -app "$CUR_DIR/application.ini" $@ else echo "Error: unable to find Firefox or XULRunner!" fi
Nous allons générer notre "application bundle" à la'ide d'un script bash.
Il est vraiment simple, nous avons juste à copier le contenu de notre appli
dans le dossier MacOS. Voici le contenu de build_mac.sh
:
#!/bin/bash # exit the script on errors set -e TMP_DIR=./tmp # get the absolute path of our TMP_DIR folder TMP_DIR=$(readlink -f -- "$TMP_DIR") # re-create the TMP_DIR folder rm -rfv "$TMP_DIR" mkdir -v "$TMP_DIR" # create the .app folder mkdir -v "$TMP_DIR/MyApp.app" # copy our bundle skeleton # (our launcher myapp-mac.sh and our symbolic link foxstub are included) cp -rv data/bundle_skelet/. "$TMP_DIR/MyApp.app/" # copy our app cp -rv myapp/* "$TMP_DIR/MyApp.app/Contents/MacOS/" # apply the correct permissions chmod -R 755 "$TMP_DIR/MyApp.app" # and copy the result in our main src folder rm -frv MyApp.app cp -Rv "$TMP_DIR/MyApp.app" ./
Ce script sera continuer par la suite. Mais pour le moment, en résultat,
nous avons créer "l'Application Bundle" MyApp.app
dans notre dossier source principal.
4.5 Empaqueter un tar.gz et un dmg
Pour distribuer notre bundle, nous devons créer une archive. Je propose 2 solutions, chacune avec ses avantages et inconvénients.
Créer un tar.gz
La première et la plus simple, est de créer un tar.gz. Ce format est bien pris en charge par Mac OSX. Le seul problème est l'utilisateur final risque de ne pas trouver celà très convivial. Voici ce qu'il a à faire :
- télécharger le fichier myapp.tar.gz.
- l'ouvrir. En fait le comportement par défaut est de décompresser l'archive dans le même dossier.
- aller dans le dossier décompressé
- déplacer par glisser/déposer le bundle où il le souhaite, par exemple dans /Applications/
Le problème est la phase 2, certains utilisateurs ne comprennent pas que l'archive est décompressée, et où.
pour créer ce tar.gz, nous ajoutons les lignes suivantes dans notre
script build_mac.sh
:
# create a sub-directory for the tar.gz creation mkdir "$TMP_DIR/myapp-1.0" # copy our bundle # (It would be possible to add other files, the license for example) cp -rv "$TMP_DIR/MyApp.app" "$TMP_DIR/myapp-1.0/" rm -fv myapp-mac-1.0.tar.gz cd "$TMP_DIR" tar -zcvf "../myapp-mac-1.0.tar.gz" myapp-1.0 cd -
Créer un dmg
la seconde solution est de créer une
archive dmg.
En fait c'est un format d'image disque.
C'est une solution habituelle sur cette plateforme, et utilisée par Firefox par exemple.
Nous pouvons créer un dmg depuis Linux, mais avec une limitation :
nous ne pouvons pas générer de dmg compressé. Enfin en fait il y a une
méthode expérimentale de le faire, que je vais vous proposé ensuite, mais elle est encore
expérimentale.
L'article le plus utile que j'ai trouvé sur ce sujet est
ce billet
de l'auteur de DMDirc. Nous allons suivre les mêmes méthodes.
La méthode la plus simple sur linux est d'utiliser mkisofs
,
ou genisoimage
.
genisoimage
est un fork du premier, utilisé par certaines distributions,
et comme un lien symbolique nommé mkisofs
est également installé par ces
distributions, j'utiliserai la commande mkisofs
dans la suite du script.
Pour l'installer sur Debian/Ubuntu :
apt-get install genisoimage
Nous ajoutons dans notre script build_mac.sh
:
# create the dmg rm -fv myapp-1.0.dmg mkisofs -V 'MyApp' -no-pad -r -apple -o "myapp-1.0.dmg" "$TMP_DIR/myapp-1.0"
Avec cette ligne, nous créons le fichier myapp-1.0.dmg
, qui contient
tous les fichiers et dossiers inclus dans le dossier $TMP_DIR/myapp-1.0
,
avec un nom de volume 'MyApp'
(le titre de la fenêtre quand le dmg
est monté sur Mac). Et tous les fichiers et dossiers dans le dmg ont les droits 755.
the 755 rights.
Compresser ce dmg
La seule solution trouvée pour réaliser cette tâche sur Linux, mentionné
dans ce
billet,
est d'utiliser le programme dmg
du projet
libdmg-hfsplus.
L'auteur dit clairement :
THE CODE HEREIN SHOULD BE CONSIDERED HIGHLY EXPERIMENTAL
traduction approximative :
CE CODE DOIT ÊTRE CONSIDÉRER COMME HAUTEMENT EXPÉRIMENTAL
Noter que le dernier commit du projet que j'ai essayé ne compilait pas
(quelqu'un a rapporté
le bug).
Et l'auteur de DMDirc rapporte un autre bug d'une version précédente.
Mais essayer sa
And the author of DMDirc report another bug in a previous version. But I have
tried its version légèrement modifiée, avec succès !
Donc vous devriez l'essayer.
Voici les lignes ajoutées à notre script :
# try to compress our dmg, if the the command <dmg> is available # see http://github.com/planetbeing/libdmg-hfsplus # and http://shanemcc.co.uk/libdmg/ if [ -x "./tools/dmg" ]; then # rename temporarily our dmg mv myapp-1.0.dmg myapp-1.0.dmg.pre # compress "./tools/dmg" dmg myapp-1.0.dmg.pre myapp-1.0.dmg rm -fv myapp-1.0.dmg.pre fi
Noter que je n'inclus pas le programme dmg
dans l'archive jointe,
vous devrez le télécharger vous même depuis les liens précédents, et le
placer dans le dossier tools
.
Créer un dmg depuis Mac OSX
pour information, pour créer un dmg directement depuis Mac OSX, il existe le programme hdiutil. Pour créer un dmg compressé et s'ouvrant automatiquement, d'après un dossier nommé "myFolder", quelque chose du genre devrait marcher :
#!/bin/bash hdiutil create -volname "myapp-1.0" -fs HFS+ -srcfolder "myFolder" -format UDRW myapp-temp.dmg hdiutil convert myapp-temp.dmg -format UDZO -imagekey zlib-level=9 -o myapp.dmg rm -fv myapp-temp.dmg
Personnaliser le dmg
il est possible de personnaliser notre dmg quand il est monté, c'est à dire la fenêtre affichée, par exemple : dimensions de l'icône, image d'arrière plan, dimensions de la fenêtre...
L'idée est de créer un dmg en lecture/écriture sur un mac, de le personnaliser
manuellement, puis de faire une copie du fichier
.DS_STORE
et de toutes les autres données. Ensuite nous utiliserons ces données lors
de la création de notre dmg à l'aide de notre script.
Cette partie ne peut être faite que manuellement et directement depuis un Mac.
Créer une image disque en lecture/écriture
Sur un Mac, ouvrez "Utilitaires de disque" dans le dossier "Utilitaires".
Cliquer sur "Nouvelle image".
Choisissez le même nom que le futur nom de volume de notre dmg
(MyApp
).
N'utilisez pas un format journalisé, pour être capable de choisir une
"petite" taille pour ce dmg. La taille importe peu, elle doit juste être
suffisante pour copier notre appli et les autres données.
Comme format
d'image, choisissez image disque en lecture et écriture
Puis cliquer sur "créer".
Ajouter nos données sur l'image disque
Maintenant montez ce dmg en cliquant dessus. Et ouvrez le.
Copiez notre appli dans la fenêtre ouverte.
Nous pouvons copier le lien symbolique vers /Applications
également,
si il existe. Sinon nous le créerons dans un terminal plus tard.
Ouvrez un terminal, depuis les Utilitaires.
Allez dans l'image disque monté :
cd /Volumes/MyApp
puis créez un dossier nommé .background
:
mkdir .background
comme ce nom commence par un point (.
), ce dossier est caché,
comme sur Linux.
copiez notre image d'arrière plan dans ce dossier :
cp pathToOurImage/background.png /Volumes/MyApp/.background/
Et, si besoin, créez le lien symbolique vers /Applications :
ln -s /Applications /Volumes/MyApp/Applications
Personnaliser l'image disque
Maintenant donnez le focus à la fenêtre du disque monté, et ouvrez
"Afficher les options de présentation" depuis le menu "Présentation"
Sélectionner Image
pour l'airrière plan, puis cliquer sur le bouton.
Nous devons sélectionner le fichier background.png dans le disque monté,
mais comme ce fichier est dans un dossier caché, nous ne pouvons pas le voir.
Pas d'inquiétude, taper Apple+Shift+G
:
puis taper /Volumes/MyApp/.background/
, puis entrée.
sélectionnez maintenant notre png.
Choissisez la taille d'icône, par exemple 128, arrangez les par aucun
,
puis déplacez les où vous le souhaitez, et adapter la taille de fenêtre
à votre arrière-plan.
Maintenant fermer cette fenêtre, et éjecter le disque. Puis monter le de nouveau, et ouvrez le.
Copier la personnalisation créée
Dans un terminal, copiez le fichier .DS_STORE
créé à la racine
du disque monté :
cp /Volumes/MyApp/.DS_STORE pathToSave/myDS_STORE
Maintenant nous avons enfin tous les fichiers nécessaires pour notre script,
nous avons juste à copier le fichier .DS_STORE et le dossier .background avec
le fichier background.png . Le dmg que nous créerons aura la même personnalisation.
Copions ces données dans nos sources :
|- samples_chapter_4/ |- data/ |- bundle_skelet/ |- dmg_extra_data/ |- .background/ |- background.png |- Applications |- .DS_STORE
Et nous ajoutons les lignes suivantes à notre script build_mac.sh
,
avant de créer le dmg :
# copy eventual extra data, by example a symbolic link to /Applications if [ $(ls -A "data/dmg_extra_data" | wc -c) -ne 0 ]; then cp -Rv data/dmg_extra_data/. "$TMP_DIR/myapp-1.0/" fi
Notez que cette personnalisation est dépendente du nom de volume utilisé,
en particulier pour l'image d'arrière plan, parce que le fichier .DS_STORE
utilise des chemins absolus vers le disque monté, et le nom de volume en fait partie.
Donc si vous changer le nom de volume, vous devez recréer manuellement cette
personnalisation...
Lancer notre script de build
Pour lancer notre script (sur Linux), et créer nos dmg et tar.gz, dans un terminal :
cd samples_chapter_4
sh ./build_mac.sh
Et, en résultat, nous avons finalement le dossier MyApp.app
,
et les fichiers myapp-1.0.dmg
et myapp-1.0.tar.gz
dans notre dossier de source principal.
Vous pouvez télécharger tous les exemples de ce chapitre 4 (Mac OSX) dans l'archive samples_chapter_4.tar.gz.
L'application myapp, de developer.mozilla.org, est dans le Domaine Public.
L'icône utilisée est issue du Tango Desktop Project, et est dans le Domaine Public.
Toutes les autres données ajoutées, et les fichiers en exemple, de ce chapitre 4, sont dans le Domain Public également.