Auteur

Auteur: Faiseur

dimanche 11 juillet 2010

Comment créer un bureau virtuel

COMMENT CRÉER UN BUREAU VIRTUEL




Tutoriel réalisé par Faiseur

Niveau: [ Intermédiaire ]




SUJETS TRAITÉS DANS CE TUTORIEL

- Il introduit au concept de procédures "callback"
- il introduit au concept de handle des fenêtres Windows
- Introduction à quatre API: ShowWindow, GetClassName, EnumWindows, lstrcmp



NOTES CONCERNANT CE TUTORIEL

La première fois qu’une API est décrite dans ce tutoriel celle-ci pointe sur son lien direct MSDN Microsoft. N'hésitez pas à vous documenter sur la MSDN pour mieux comprendre le fonctionnement des API utilisées.

Définition de certains termes employés:

Handle : une valeur permettant à Windows d’identifier, dans notre cas, une fenêtre.

Callback : Il s'agit d'une fonction - ou procédure - qui sera automatiquement appelée quand un événement particulier se produit.




DESCRIPTION DU PROGRAMME


Les applications permettant de créer des bureaux virtuels ont eu un certain succès il y a quelques années. D'un premier abord il peut sembler compliqué de mettre en place un tel système mais la création de deux bureaux virtuels uniquement, par exemple, est plutôt simple en se servant de quelques API. De plus ce type de tutoriel est à ma connaissance inconnu en assembleur, ce qui m'a motivé à en proposer un.

Notre démo, dont le code source est fournit en fichier joint plus bas, va permettre à l’utilisateur de changer virtuellement de bureau. Chaque bureau virtuel disposera de ses propres paramètres ; c’est-à-dire qu’il intégrera des fenêtres de navigateurs que l’autre bureau n’affichera pas. Il en sera de même pour les dossiers ouverts par l’utilisateur, qui seront propres à chaque bureau.


Code:

SqueletteGUI.asm
Code:


   comment *
   Exemple de bureau virtuel (implémenté dans Horizon Toolbar) par Faiseur
   *
   .386
    .model flat, stdcall
    option casemap :none 

    include \masm32\include\windows.inc
   include \masm32\macros\macros.asm

   include \masm32\include\kernel32.inc
   include \masm32\include\user32.inc
   include \masm32\include\masm32.inc
   include \masm32\include\comctl32.inc
 

   includelib \masm32\lib\kernel32.lib
   includelib \masm32\lib\user32.lib
   includelib \masm32\lib\masm32.lib
   includelib \masm32\lib\comctl32.lib
 
   include Proc/Bureauvirtuel.asm
 
 
   .const
 
   IDD_DIALOG1         equ 1000
   IDC_BTN1         equ 1001
   IDC_BUREAU1         equ 1002
   IDC_BUREAU2         equ 1003
 
   .data?
 
   hInstance         dd ?

   .code
   DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
 
      Switch uMsg
      Case WM_COMMAND
         mov      edx,wParam
         movzx   eax,dx
         shr      edx,16
         .if eax==IDC_BTN1
            invoke SendMessage,hWin,WM_CLOSE,NULL,NULL
         .elseif eax==IDC_BUREAU1
            .if virtualdesktop == 2
               mov countf,1
                 invoke EnumWindows,addr EnumExplorerProc2,0    
               fn SetDlgItemText,hWin,1002,"Bureau virtuel 1 actif"            
               fn SetDlgItemText,hWin,1003,"Bureau virtuel  2"
                 mov virtualdesktop,1 
              .endif          
         .elseif eax==IDC_BUREAU2       
            .if virtualdesktop == 1    
               mov countf,1
                 invoke EnumWindows,addr EnumExplorerProc,0    
               fn SetDlgItemText,hWin,1003,"Bureau virtuel 2 actif"    
               fn SetDlgItemText,hWin,1002,"Bureau virtuel  1"
                 mov virtualdesktop,2 
            .endif
         .endif
 
      Case WM_CLOSE
         invoke EndDialog,hWin,0
      default
         return FALSE
      Endsw
         return TRUE
   DlgProc endp
 
   start: 
      mov hInstance,FUNC(GetModuleHandle,0)
      invoke InitCommonControls  
      invoke DialogBoxParam,hInstance,IDD_DIALOG1,NULL,addr DlgProc,NULL
      exit
   end start 


Bureauvirtuel.asm
Code:


.data
   hfolder                  dd 20 dup(0) ; fenêtres du bureau virtuel 2
   gfolder                  dd 20 dup(0) ; fenêtres du bureau virtuel 1
   countf                  dd 1
   virtualdesktop            dd 1 

.code


EnumExplorerProc proc hwnd:DWORD,lParam:DWORD
   LOCAL szWinBuf[261]:BYTE
 
   invoke GetClassName,hwnd,addr szWinBuf,sizeof szWinBuf
   fn lstrcmp,addr szWinBuf,"CabinetWClass"
   test eax,eax 
   je @F
   fn lstrcmp,addr szWinBuf,"MozillaUIWindowClass"
   test eax,eax 
   je @F 
   fn lstrcmp,addr szWinBuf,"IEFrame"
   test eax,eax 
   je @F 
   return TRUE
 
   @@:
      mov eax,hwnd
      .if eax != gfolder[0] && eax != gfolder[4] && eax != gfolder[8] && eax != gfolder[12] && eax != gfolder[16] && eax != gfolder[20] && eax != gfolder[24] && eax != gfolder[28] && eax != gfolder[32] \
      && eax != gfolder[36] && eax != gfolder[40] && eax != gfolder[44] && eax != gfolder[48] && eax != gfolder[52] && eax != gfolder[56] && eax != gfolder[60] && eax != gfolder[64] && eax != gfolder[68]
         .if countf < 19
            .if countf ==1
               m2m hfolder[0],hwnd       
            .elseif countf ==2
               m2m hfolder[4],hwnd 
            .elseif countf ==3
               m2m hfolder[8],hwnd 
            .elseif countf ==4
               m2m hfolder[12],hwnd 
            .elseif countf ==5
               m2m hfolder[16],hwnd 
            .elseif countf ==6
               m2m hfolder[20],hwnd 
            .elseif countf ==7
               m2m hfolder[24],hwnd 
            .elseif countf ==8
               m2m hfolder[28],hwnd 
            .elseif countf ==9
               m2m hfolder[32],hwnd 
            .elseif countf ==10
               m2m hfolder[36],hwnd       
            .elseif countf ==11
               m2m hfolder[40],hwnd 
            .elseif countf ==12
               m2m hfolder[44],hwnd 
            .elseif countf ==13
               m2m hfolder[48],hwnd 
            .elseif countf ==14
               m2m hfolder[52],hwnd 
            .elseif countf ==15
               m2m hfolder[56],hwnd 
            .elseif countf ==16
               m2m hfolder[60],hwnd 
            .elseif countf ==17
               m2m hfolder[64],hwnd 
            .elseif countf ==18
               m2m hfolder[68],hwnd                                                       
            .endif 
            inc countf
               invoke ShowWindow,hwnd,SW_HIDE
            return TRUE
         .endif
      .endif 
   invoke ShowWindow,hwnd,SW_SHOW    
   return TRUE
EnumExplorerProc endp 

EnumExplorerProc2 proc hwnd:DWORD,lParam:DWORD
   LOCAL szWinBuf[261]:BYTE
 
   invoke GetClassName,hwnd,addr szWinBuf,sizeof szWinBuf
   fn lstrcmp,addr szWinBuf,"CabinetWClass"
   test eax,eax 
   je @F 
   fn lstrcmp,addr szWinBuf,"MozillaUIWindowClass"
   test eax,eax 
   je @F 
   fn lstrcmp,addr szWinBuf,"IEFrame"
   test eax,eax 
   je @F 
   return TRUE
 
   @@: 
      mov eax,hwnd
      .if eax != hfolder[0] && eax != hfolder[4] && eax != hfolder[8] && eax != hfolder[12] && eax != hfolder[16] && eax != hfolder[20] && eax != hfolder[24] && eax != hfolder[28] && eax != hfolder[32] \
      && eax != hfolder[36] && eax != hfolder[40] && eax != hfolder[44] && eax != hfolder[48] && eax != hfolder[52] && eax != hfolder[56] && eax != hfolder[60] && eax != hfolder[64] && eax != hfolder[68]
         .if countf < 19
            .if countf ==1
               m2m gfolder[0],hwnd       
            .elseif countf ==2
               m2m gfolder[4],hwnd 
            .elseif countf ==3
               m2m gfolder[8],hwnd 
            .elseif countf ==4
               m2m gfolder[12],hwnd 
            .elseif countf ==5
               m2m gfolder[16],hwnd 
            .elseif countf ==6
               m2m gfolder[20],hwnd 
            .elseif countf ==7
               m2m gfolder[24],hwnd 
            .elseif countf ==8
               m2m gfolder[28],hwnd 
            .elseif countf ==9
               m2m gfolder[32],hwnd 
            .elseif countf ==10
               m2m gfolder[36],hwnd       
            .elseif countf ==11
               m2m gfolder[40],hwnd 
            .elseif countf ==12
               m2m gfolder[44],hwnd 
            .elseif countf ==13
               m2m gfolder[48],hwnd 
            .elseif countf ==14
               m2m gfolder[52],hwnd 
            .elseif countf ==15
               m2m gfolder[56],hwnd 
            .elseif countf ==16
               m2m gfolder[60],hwnd 
            .elseif countf ==17
               m2m gfolder[64],hwnd 
            .elseif countf ==18
               m2m gfolder[68],hwnd                                                       
            .endif 
            inc countf
            invoke ShowWindow,hwnd,SW_HIDE
            return TRUE
         .endif
      .endif 
   invoke ShowWindow,hwnd,SW_SHOW 
   return TRUE
EnumExplorerProc2 endp 




Voici l'intelligence du programme:

1. Lorsque l'utilisateur veut passer au bureau virtuel 2:

- Réinitialiser puis mémoriser les identifiants des fenêtres ouvertes par le bureau virtuel 1 (il s'agit du bureau ouvert par défaut par l'utilisateur)

- Cacher toutes les fenêtres des navigateurs et de l'explorer du bureau 1

- Afficher les fenêtres (les handle) appartenant au bureau 2

2. Lorsque l'utilisateur veut passer au bureau virtuel 1:

- Réinitialiser puis mémoriser les identifiants (handle) des fenêtres ouvertes par le bureau virtuel 2

- Cacher toutes les fenêtres des navigateurs et de l'explorer du bureau 2

- Afficher les fenêtres (les handle) appartenant au bureau 1



ANALYSE DU CODE

Passage du bureau 1 au bureau 2:

Lorsque l’utilisateur veut afficher le bureau virtuel 2 il commence par appuyer sur le bouton correspondant IDC_BUREAU2
Code:
elseif eax==IDC_BUREAU2



On vérifie si nous sommes bien sur le bureau virtuel 1. Si c'est le cas nous passons à la suite…
Code:

         .if virtualdesktop == 1



Initialisation du compteur des handle.Ce compteur sera utilisé dans les procédures callback EnumExplorerProc et EnumExplorerProc2.
Code:

            mov countf,1



Ceci le cœur de tout le programme. Le passage d’un bureau virtuel à l’autre se fait avec cette fonction Api que nous allons détailler plus bas.
Code:

              invoke EnumWindows,addr EnumExplorerProc,0   



On termine en modifiant le texte des deux boutons et en signifiant à l'application que nous sommes passés au bureau virtuel 2
Code:

            invoke SetDlgItemText,hWin,1003,"Bureau virtuel 2 actif"    
            invoke SetDlgItemText,hWin,1002,"Bureau virtuel  1"
              mov virtualdesktop,2 
         .endif





INTRODUCTION AUX PROCEDURES CALLBACK


On utilise l'API EnumWindows pour récupérer l'handle (identifiant unique) de toutes les fenêtres ouvertes sur le bureau de l'utilisateur.

Cette API nous envoie à une procédure callback qui correspond, dans le programme, à "EnumExplorerProc" "ou EnumExplorerProc2".

"EnumExplorerProc" "ou EnumExplorerProc2" sont appelées chaque fois qu'un événement est trouvé par l'API. "EnumExplorerProc" "et EnumExplorerProc2" vont alors traiter cet événement puis rendre la main à l’API jusqu’au prochain événement.

"EnumExplorerProc" s'occupe de la procédure nécessaire pour afficher le bureau virtuel 2.

"EnumExplorerProc2" s'occupe de la procédure nécessaire pour afficher le bureau virtuel 1.

Dans notre cas l'API EnumWindows appellera nos procédures à chaque fois qu'une fenêtre sera trouvée. Lorsque cette API donne la main à "EnumExplorerProc" "ou EnumExplorerProc2" elle leur envoie, en paramètre, une précieuse information: l'identifiant unique de la fenêtre (handle). C'est avec cette information que nous pourrons tout faire: cacher les fenêtres ou les faire réapparaître.


Voici l'appel de cette fonction dans notre programme:

Pour passer du bureau virtuel 1 au bureau virtuel 2…
Code:
invoke EnumWindows,addr EnumExplorerProc2,0



Pour passer du bureau virtuel 2 au bureau virtuel 1…
Code:
invoke EnumWindows,addr EnumExplorerProc,0   




ANANYLSE DE LA PROCEDURE CALLBACK « EnumExplorerProc »


Au début de nos procédures callback nous trouvons :
Code:
invoke GetClassName,hwnd,addr szWinBuf,sizeof szWinBuf



GetClassName permet de retrouver le nom de la classe d'une fenêtre trouvée par l'API EnumWindows. Dans notre cas on vérifie par comparaison de nom (l'API lstrcmp permet de comparer deux chaînes de caractères afin de vérifier si elles sont équivalentes) qu'il s'agit soit:

d'une fenêtre de l'explorer (elles ont pour nom "CabinetWClass")...
Code:
   invoke lstrcmp,addr szWinBuf,"CabinetWClass"


d'une fenêtre de Firefox (elles ont pour nom "MozillaUIWindowClass")...
Code:
   invoke lstrcmp,addr szWinBuf,"MozillaUIWindowClass"



d'une fenêtre d'Internet Explorer (elles ont pour nom "IEFrame")...
Code:
   invoke lstrcmp,addr szWinBuf,"IEFrame"


Si une de ces fenêtres sont trouvées on passe à la suite de la procédure callback, sinon on rend la main à EnumWindows.

On vérifie ici si le handle de la fenêtre trouvée est déjà utilisé dans le bureau 2
Code:
 .if eax != hfolder[0] && eax != hfolder[4] etc


Complément: "&&" est une manière d'écrire la condition "ET" logique

Si c’est le cas on affiche la fenêtre trouvée du bureau 2 avec l’API ShowWindow :
Code:
invoke ShowWindow,hwnd,SW_SHOW


Si ce n’est pas le cas on mémorise la fenêtre comme appartenant au bureau virtuel 1
Code:
 m2m gfolder[xx],hwnd


Puis on la rend invisible pour l’utilisateur
Code:
invoke ShowWindow,hwnd,SW_HIDE


Au final on rend la main à l’API EnumWindows
Code:
return TRUE




EN RÉSUMÉ

- Cette procédure callback aura donc été appelée le nombre de fois correspondant au nombre de fenêtres trouvées par l'API EnumWindows.

- Nous aurons caché les fenêtres de l'explorer et des navigateurs du bureau 1

- Nous aurons mémorisé l'identifiant (handle) des fenêtres de l'explorer et des navigateurs du bureau 1

- Nous aurons ouvert, à partir de leurs identifiants, les fenêtres de l'explorer et des navigateurs du bureau 2



POUR REVENIR AU BUREAU VIRTUEL 1 ?

Il sera nécessaire d’inverser le sens du test des handles lorsque nous voulons retrouver le bureau virtuel 1. C’est pourquoi une deuxième procédure callback est utilisée, « EnumExplorerProc2 », pour revenir à l’état initial. La différence se fait à cet endroit :

Code:
.if eax != gfolder[0] && eax != gfolder[4] etc


Au lieu de vérifier si une fenêtre trouvée correspond à une fenêtre du bureau virtuel 2, nous vérifions si la fenêtre trouvée correspond à une fenêtre du bureau virtuel 1. Si ce n’est pas le cas nous la mémorisons en tant que fenêtre du bureau virtuel 2 :
Code:
m2m hfolder[xx],hwnd


Ainsi, gfolder récupère les identifiants (handle) des fenêtres du bureau virtuel 1, hfolder les identifiants (handle) des fenêtres du bureau virtuel 2.




AMELIORATIONS POSSIBLES

On peut évidemment inclure une compatibilité avec les fenêtres d'autres navigateurs bien que les deux cités plus haut sont utilisés dans la grande majorité des cas. Inclure la fermeture des fenêtres d'autres programmes en cours, dont nous ne connaissons pas forcément l'utilisation, n'est évidemment pas conseillé pour diverses raisons. C'est pour cela que le bureau virtuel a ses limites mais offre une bonne versatilité en usage courant. Proposer d'autres bureaux virtuels serait également un plus.



CONCERNANT LA COMPATIBILITE DU CODE SOURCE

Les sources sont en syntaxe Masm/JWasm. Mais si vous utilisez Masm (ml.exe) une erreur va apparaître lors de la compilation du code source.

La ligne ".if eax != gfolder[0] && eax != gfolder[4] ..." est trop longue. Deux possilibités:

1. Utilisez comme moi JWasm, qui n'est pas aussi limité par la longueur du code sur une ligne

2. Séparer cette ligne en plusieurs parties avec la commande "\"





Juillet 2010 - Faiseur

0 commentaires:

Enregistrer un commentaire