This post exists also in english
Ce billet fait partie d'une série sur comment déployer une application XULRunner. Voir le préambule pour le contexte.
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.