Distribuer votre appli XULRunner
3 - Windows
- 3.1 Icônes pour Windows
- 3.2 Créer un lanceur (batch ou C)
- 3.3 Créer un script NSIS
- 3.4 Créer l'installeur
3.1 Icônes pour Windows
Nous avons besoin d'une icône au format .ico. Elle sera utilisée par nos fenêtres, et par notre lanceur.
Il est facile de faire un fichier .ico sur Linux, nous avons juste besoin
du programme icotool
, disponible dans le paquet icoutils
.
Sur Debian/Ubuntu :
apt-get install icoutils
Puis nous créons le fichier ico à partir de plusieurs png de différentes tailles, 16x16 px, 32x32 px, et 48x48 px. Il serait possible d'utiliser plus de png, avec différentes profondeurs de couleur par exemple.
icotool -c -o icon.ico icon16.png icon32.png icon48.png
Vous trouverez un script bash avec cette ligne de commande dans l'archive
example de ce chapitre 3 (dans samples_chapter_3/data/icons/
).
Pour utiliser notre icône pour nos fenêtres, plutôt que celle de firefox
ou de xulrunner, nous avons juste à mettre cette icône, renommée
default.ico
, dans le dossier icons/default
situé
dans notre dossier chrome principal.
Cette icône sera utilisée par tous les <window>
XUL
sans un attribut id
.
Mais le <window>
XUL principal de myapp a l'attribut
id="main"
, donc pour cette fenêtre nous devons avoir une
icône nommée main.ico
, située dans le même dossier.
|- samples_chapter_3/ |- myapp/ |- chrome |- icons/ |- default/ |- default.ico |- main.ico
3.2 Créer un lanceur (batch ou C)
Le lanceur le plus simple est un script batch. Voici le
contenu de myapp.bat
:
set CUR_DIR=%~dp0 START firefox -app $CUR_DIR\application.ini
Ce fichier doit être placé dans le dossier principal de notre appli.
Ça marche, il lance notre appli via le fichier application.ini de notre
appli.
Mais il se passe quelque chose de gênant, une fenêtre noire de commande
est également ouverte.
Pour éviter ce defaut, nous allons créer un lanceur en C.
Nous utiliserons le code de XAL
(XUL Application Launcher). C'est un programme léger en C, sous licence
MIT,
il lance une application XULRunner avec Firefox, avec l'argument -app
et application.ini
.
Il doit être placé dans le dossier principal de l'application (comme le batch
précédent). Bonus, il prend en charge d'éventuels arguments supplémentaires
(comme -jsconsole
), quand il est utilisé en ligne de commande.
Et nous pouvons ajouter notre icône, et quelques autres informations sur
l'application, via un fichier .rc.
Cet exécutable peut etre compilé avec n'importe quel compilateur C, son
code est indépendant du code de Mozilla. Personnellement je le compile
avec MinGW, sur Linux,
avec succés.
Si vous voulez utiliser un autre compilateur, éditer le fichier build,
et adapter les variables CC
et RESCOMP
.
pour installer MinGW sur Debian/Ubuntu:
apt-get install mingw32
Je ne publie pas ici le code source C, vous le trouverez dans l'archive relative à ce chapitre, où dans sa page dédiée.
Nous pouvons personnaliser ce lanceur, en utilisant un
fichier
resource
(.rc
), insérer notre icône et spécifier quelques informations
(vendeur, nom de l'appli, version,...).
Voici le contenu du fichier myapp-res.rc :
APPLICATION_ICON ICON "myapp.ico" 1 VERSIONINFO FILEVERSION 0,1,0,0 PRODUCTVERSION 0,0,0,0 BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "000004B0" BEGIN VALUE "Comments", "Published under the MPL 1.1/GPL 2.0/LGPL 2.1 licenses" VALUE "CompanyName", "John Doe Organization" VALUE "FileDescription", "MyApp" VALUE "FileVersion", "1.0.0" VALUE "InternalName", "MyApp" VALUE "LegalCopyright", "(c) 2010 John Doe" VALUE "ProductName", "MyApp" VALUE "ProductVersion", "0.0.0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x000, 1200 END END
Quelques détails sur ce contenu :
La ligne APPLICATION_ICON ICON "myapp.ico"
insère notre icône
Les entrées FILEVERSION
et PRODUCTVERSION
utilisent un format spécial, en résumé
entier virgule entier virgule entier virgule entier
.
La ligne VALUE "Translation", 0x000, 1200
spécifie que les
chaînes (string) utilisées
dans les champs d'information sont en Unicode.
Nous pouvons compiler ce lanceur, en utilisant notre fichier resource, avec
le script build.sh
fourni avec XAL :
sh build.sh myapp myapp-res.rc
Après compilation, la taille du fichier créé est de ~21Ko, donc vraiment léger (notez que le poids de l'icône est incluse).
3.3 Créer un script NSIS
Il existe plusieurs solution pour créer des installeurs pour Windows.
Ici nous utiliserons NSIS,
car : il est open source, nous pouvons construire notre installeur
depuis Linux, et qu'il est facile et puissant.
Firefox lui-même utilise NSIS pour son installeur Windows.
Pour installer les outils NSIS sur Debian/Ubuntu :
apt-get install nsis
NSIS utilise son propre language de script pour créer les installeurs.
Nous pouvons utiliser des pages par défaut, en créer de nouvelles personnalisées,
gérer les fichiers lors de l'installation/désinstallation, agir sur le Registre
Windows,...
Je ne vais pas faire une documentation complète sur NSIS ici, voyez leur
wiki,
il y a beaucoup d'explications et d'exemples.
Vous devriez avoir des exemples et une doc disponibles en local, une fois
nsis installé, dans
/usr/share/doc/nsis/Examples
et /usr/share/doc/nsis/Doc
.
Mais voilà le principal script proposé, et j'explique par la suite quelles actions sont réalisées :
!define PRODUCT_NAME "MyApp" !define PRODUCT_INTERNAL_NAME "myapp" !define PRODUCT_VERSION "1.0" !define PRODUCT_WIN_VERSION "1.0.0.0" !define MESSAGEWINDOW_NAME "${PRODUCT_NAME}MessageWindow" !define HKEY_ROOT "HKLM" !define UN_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" !define LICENSE_PATH "${PRODUCT_INTERNAL_NAME}\LICENSE.txt" !define INSTALLER_NAME "${PRODUCT_NAME}-${PRODUCT_VERSION}-install.exe" !define TMP_UNINSTALL_EXE "${PRODUCT_INTERNAL_NAME}_uninstall.exe" ;-------------------------------- ;Variables ; The name of the product installed Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" ; The file to write OutFile "${INSTALLER_NAME}" SetCompressor /final /solid lzma ShowInstDetails show ShowUninstDetails show ; The default installation directory InstallDir $PROGRAMFILES\${PRODUCT_NAME} ; Request application privileges for Windows Vista RequestExecutionLevel admin Var Shortcuts_Dialog Var Shortcuts_Label Var Shortcuts_SM_Checkbox Var Shortcuts_SM_Checkbox_State Var Shortcuts_D_Checkbox Var Shortcuts_D_Checkbox_State Var Previous_Uninstall Var Previous_Uninstall_dir Var TempUninstallPath !include "MUI2.nsh" !include "FileFunc.nsh" VIProductVersion "${PRODUCT_WIN_VERSION}" VIAddVersionKey "ProductName" "${PRODUCT_NAME}" ;VIAddVersionKey "CompanyName" "${CompanyName}" ;VIAddVersionKey "LegalTrademarks" "${BrandShortName} is a Trademark of" ;VIAddVersionKey "LegalCopyright" "${CompanyName}" VIAddVersionKey "LegalCopyright" "" VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}" VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}" VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer" VIAddVersionKey "OriginalFilename" "${INSTALLER_NAME}" !define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_INTERNAL_NAME}.exe" ;-------------------------------- ; Pages !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "${LICENSE_PATH}" !insertmacro MUI_PAGE_DIRECTORY Page custom onShortcutsPageCreate !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "French" !include ./l10n/fr.nsh !include ./l10n/en_US.nsh ;-------------------------------- Function .onInit ; an eventual previous version of the app should not be currently running. ; Abort if any. ; Explanation, when the application is running, a window with the className ; productnameMessageWindow exists FindWindow $0 "${MESSAGEWINDOW_NAME}" StrCmp $0 0 +3 MessageBox MB_OK|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. Please close it first" /SD IDOK Abort StrCpy $Shortcuts_SM_Checkbox_State 1 StrCpy $Shortcuts_D_Checkbox_State 1 FunctionEnd Function un.onInit ; see Function .onInit FindWindow $0 "${MESSAGEWINDOW_NAME}" StrCmp $0 0 +3 MessageBox MB_OK|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. Please close it first" /SD IDOK Abort FunctionEnd ; custom page creation, for the shortcuts installation, using nsDialog Function onShortcutsPageCreate !insertmacro MUI_HEADER_TEXT $(l10n_SHORTCUTS_PAGE_TITLE) \ $(l10n_SHORTCUTS_PAGE_SUBTITLE) nsDialogs::Create 1018 Pop $Shortcuts_Dialog ${If} $Shortcuts_Dialog == error Abort ${EndIf} ${NSD_CreateLabel} 0 6 100% 12u $(l10n_CREATE_ICONS_DESC) Pop $Shortcuts_Label ${NSD_CreateCheckbox} 15u 20u 100% 10u $(l10n_ICONS_STARTMENU) Pop $Shortcuts_SM_Checkbox GetFunctionAddress $0 OnSMCheckbox nsDialogs::OnClick $Shortcuts_SM_Checkbox $0 ${If} $Shortcuts_SM_Checkbox_State == ${BST_CHECKED} ${NSD_Check} $Shortcuts_SM_Checkbox ${EndIf} ${NSD_CreateCheckbox} 15u 40u 100% 10u $(l10n_ICONS_DESKTOP) Pop $Shortcuts_D_Checkbox GetFunctionAddress $0 OnDCheckbox nsDialogs::OnClick $Shortcuts_D_Checkbox $0 ${If} $Shortcuts_D_Checkbox_State == ${BST_CHECKED} ${NSD_Check} $Shortcuts_D_Checkbox ${EndIf} nsDialogs::Show FunctionEnd ; event when the Start Menu shortcut is (un)checked in the custom page Function OnSMCheckbox ${NSD_GetState} $Shortcuts_SM_Checkbox $Shortcuts_SM_Checkbox_State Pop $0 # HWND FunctionEnd ; event when the Desktop shortcut is (un)checked in the custom page Function OnDCheckbox ${NSD_GetState} $Shortcuts_D_Checkbox $Shortcuts_D_Checkbox_State Pop $0 # HWND FunctionEnd Function WriteUninstallReg WriteRegStr ${HKEY_ROOT} ${UN_KEY} "DisplayName" \ "${PRODUCT_NAME} (${PRODUCT_VERSION})" WriteRegStr ${HKEY_ROOT} ${UN_KEY} "UninstallString" \ "$INSTDIR\uninstall.exe" WriteRegStr ${HKEY_ROOT} ${UN_KEY} "QuietUninstallString" \ "$INSTDIR\uninstall.exe /S" WriteRegStr ${HKEY_ROOT} ${UN_KEY} "InstallLocation" \ "$INSTDIR" WriteRegStr ${HKEY_ROOT} ${UN_KEY} "DisplayIcon" \ "$INSTDIR\${PRODUCT_INTERNAL_NAME}.exe" WriteRegStr ${HKEY_ROOT} ${UN_KEY} "DisplayVersion" \ "${PRODUCT_VERSION}" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD ${HKEY_ROOT} ${UN_KEY} "EstimatedSize" "$0" FunctionEnd ; The stuff to install Section "" ; uninstall an eventual previous installation ReadRegStr $Previous_Uninstall ${HKEY_ROOT} ${UN_KEY} "UninstallString" ClearErrors ${If} $Previous_Uninstall != "" StrCpy $Previous_Uninstall_dir $Previous_Uninstall ${GetParent} $Previous_Uninstall $Previous_Uninstall_dir IfFileExists "$Previous_Uninstall" myUninstallPrevious myInstall ${Else} goto myInstall ${EndIf} myUninstallPrevious: ; copy the previous uninstaller into TEMP ClearErrors StrCpy $TempUninstallPath "$TEMP\${TMP_UNINSTALL_EXE}" CopyFiles /SILENT "$Previous_Uninstall" "$TempUninstallPath" IfErrors myInstall ClearErrors ExecWait '"$TempUninstallPath" /S _?=$Previous_Uninstall_dir' ClearErrors Delete "$TempUninstallPath" ;MessageBox MB_OK "UNINSTALL: finished" myInstall: SetOutPath $INSTDIR ; copy the files File /r ${PRODUCT_INTERNAL_NAME}\* WriteUninstaller "uninstall.exe" Call WriteUninstallReg ${If} $Shortcuts_SM_Checkbox_State == ${BST_CHECKED} CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" \ "$INSTDIR\${PRODUCT_INTERNAL_NAME}.exe" ${EndIf} ${If} $Shortcuts_D_Checkbox_State == ${BST_CHECKED} CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" \ "$INSTDIR\${PRODUCT_INTERNAL_NAME}.exe" ${EndIf} SectionEnd ;-------------------------------- ; Uninstaller Section "Uninstall" MessageBox MB_OK|MB_ICONEXCLAMATION "$INSTDIR" /SD IDOK ; Remove installed files and uninstaller !include ./uninstall_files.nsi Delete "$INSTDIR\uninstall.exe" ; remove installed directories !include ./uninstall_dirs.nsi RMDir /r "$INSTDIR\extensions" ; Remove shortcuts, if any Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk" Delete "$DESKTOP\${PRODUCT_NAME}.lnk" ;TODO remove eventual quicklaunch Too ; Remove the installation directory used (if empty) RMDir "$INSTDIR" ; and delete the registry key for uninstall DeleteRegKey ${HKEY_ROOT} ${UN_KEY} SectionEnd
Quelques explications sur ce script :
Il utilise le thème par défaut "modern". Mais il serait possible de le personnaliser.
Certaines parties sont localisées ("traduites"), en insérant d'autres scripts nsis (décrits plus loin).
Il utilise quelques pages nsis par défaut, et une personnalisée pour la création des raccourcis sur le bureau et dans le menu démarrez.
L'installation et la désinstallation sont annulées si notre application est
actuellement lancée.
En fait, quand notre application est en fonctionnement, XULRunner/Firefox crée
une fenêtre Windows native avec une classe MyAppMessageWindow
,
le nom de cette classe est la valeur du champ Name
dans application.ini
concaténée avec "MessageWindow".
Le script vérifie juste si une telle fenêtre avec ce nom de classe est ouverte,
et annule le traitement.
Il crée quelques entrées minimales dans La Base de Registre, pour permettre la désinstallation du programme avec l'outil "ajouter/supprimer des programmes" de Windows.
Si notre appli est déjà installée, la précédente version est désinstallée avant la nouvelle installation, en utilisant le désinstalleur précédent. Et, pour être plus précis, ce précédent désinstalleur est copié et lancé depuis le dossier "Temp" de Windows.
Pour la désinstallation, nous avons besoin de la liste complète des fichiers
et dossiers installés.
Ces listes seront créés dynamiquement plus tard, pour le moment dans ce script
nous les insérons en tant que scripts nsis supplémentaires
(uninstall_files.nsi
et uninstall_dirs.nsi
).
Maintenant voici le contenu de l'un des scripts localisés ("traduit"), en_US.nsh :
;!!! IMPORTANT: this must file must be edited with ANSI charset (in fact the ; Windows CP-1252 charset), but if there's no special character, UTF-8 is ok, ; because it's a subset ;) LangString l10n_SHORTCUTS_PAGE_TITLE ${LANG_ENGLISH} \ "Set Up Shortcuts" LangString l10n_SHORTCUTS_PAGE_SUBTITLE ${LANG_ENGLISH} \ "Create Program Icons" LangString l10n_CREATE_ICONS_DESC ${LANG_ENGLISH} \ "Create icons for ${PRODUCT_NAME}:" LangString l10n_ICONS_DESKTOP ${LANG_ENGLISH} \ "On my &Desktop" LangString l10n_ICONS_STARTMENU ${LANG_ENGLISH} \ "In my &Start Menu Programs folder"
Nous devons faire attention à l'encodage des ces fichiers, celui-ci en anglais
ne devrait pas poser de problème, mais celui en français doit être édité
avec le charset Windows CP-1252
par exemple.
Ces variables sont utilisées simplement dans notre script principal avec,
par exemple, $(l10n_SHORTCUTS_PAGE_TITLE)
.
Notez que l'esperluette &
dans les définitions de chaîne
définissent des raccourcis clavier pour les contrôles dans l'interface
utilisateur, ici il y a alt+D
et alt+S
.
3.4 Créer l'installeur
Nous avons maintenant tous les fichiers pour créer l'installeur. Voyons l'arborescence de nos sources :
|-samples_chapter_3 |- data/ |- icons/ |- icon.ico |- myapp.bat |- myapp-res.rc |- myapp/ |- win |- nsis/ |- l10n/ |- en_US.nsh |- fr.nsh |- myapp.nsi |- xal-src/ |- build.sh |- clean.sh |- main.c
Nous devons construire notre lanceur à partir des sources C et de nos
données (icône et resource).
Et créer 2 scripts nsis additionnels, pour lister les fichiers de notre appli.
Puis nous pourrons créer notre installeur avec makensis
.
Nous allons faire celà dans un dossier temporaire. Voici le script, nommé
build_win.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" # copy our app cp -rv myapp "$TMP_DIR/" # copy the XAL launcher sources cp -rv win/xal-src "$TMP_DIR/xal" # and our icon and resource file cp -v data/icons/icon.ico "$TMP_DIR/xal/myapp.ico" cp -v data/myapp-res.rc "$TMP_DIR/xal/" # build the launcher cd "$TMP_DIR/xal/" sh build.sh myapp myapp-res.rc cd - # copy the launchers in the right folder cp -v data/myapp.bat "$TMP_DIR/myapp/" cp -v "$TMP_DIR/xal/myapp.exe" "$TMP_DIR/myapp/" # delete the xal sources rm -rv "$TMP_DIR/xal" # create the nsis script listing the files to unsinstall cd "$TMP_DIR/myapp" find . -maxdepth 1 -type f > ../uninstall_files.nsi sed -i -r "s|^\./(.*)$| Delete \"\$INSTDIR\\\\\1\"|" ../uninstall_files.nsi # and the list of directory ls -d */ > ../uninstall_dirs.nsi sed -i -r "s|^(.*)/$| RMDir /r \"\$INSTDIR\\\\\1\"|" ../uninstall_dirs.nsi cd - # copy the other nsis scripts cp -rv win/nsis/* "$TMP_DIR/" # and create the installer makensis "$TMP_DIR/myapp.nsi" # finally, copy our installer in the root sources dir cp -v "$TMP_DIR/MyApp-1.0-install.exe" ./ echo "Windows installer for myapp created."
Pour lancer ce script, dans un terminal :
cd samples_chapter_3
sh ./build_win.sh
Et en résultat nous avons finalement le fichier
MyApp-1.0-install.exe
dans le dossier
samples_chapter_3
:) .
Vous pouvez télécharger tous les exemples de ce chapitre 3 (Windows) dans l'archive samples_chapter_3.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.
le lanceur C XUL App Launcher (XAL) est sous licence MIT.
Toutes les autres données ajoutées, et les fichiers en exemple, de ce chapitre 3, sont dans le Domain Public également.