Introduction:
Il peut être utile de limiter une tache ou l'utilisation d'un exécutable dans le temps: limiter le temps d'utilisation d'un jeu pour un enfant (ou un adulte ;-), limiter l'utilisation d'un programme suivant un abonnement ou un crédit de temps...
Après des tests d’accès aux ressources windows via la librairie WMI, puis l'utilisation de multiples de threads, j'ai finalement utilisé des utilitaires de base de windows comme tasklist et taskkill pour accéder aux processus windows: simple et efficace...
Solution:
LimitTask.py se lance comme une commande avec 3 paramètres: le premier donne le chemin complet de l'exécutable à surveiller, le second donne le temps autorisé d'utilisation de cet exécutable et enfin le troisième paramètre permet la remise à zéro régulière de ce décompte de temps d'utilisation (abonnement).
L'algorithme est décrit ci-dessous:
Il peut être utile de limiter une tache ou l'utilisation d'un exécutable dans le temps: limiter le temps d'utilisation d'un jeu pour un enfant (ou un adulte ;-), limiter l'utilisation d'un programme suivant un abonnement ou un crédit de temps...
Après des tests d’accès aux ressources windows via la librairie WMI, puis l'utilisation de multiples de threads, j'ai finalement utilisé des utilitaires de base de windows comme tasklist et taskkill pour accéder aux processus windows: simple et efficace...
Solution:
LimitTask.py se lance comme une commande avec 3 paramètres: le premier donne le chemin complet de l'exécutable à surveiller, le second donne le temps autorisé d'utilisation de cet exécutable et enfin le troisième paramètre permet la remise à zéro régulière de ce décompte de temps d'utilisation (abonnement).
L'algorithme est décrit ci-dessous:
Remarques:
-L'utilisation d'une sauvegarde régulière des paramètres de temps, via le fichier LTsave.txt, est indispensable si, par exemple, on redémarre son PC. La relance de LimitTask permet de ne pas perdre la référence du temps d'utilisation/jeu déjà entamés.
-L'utilisation de boite de dialog éphémère (MessageBoxTimeout dans la class LimitTask) est une façon simple de signaler une action (ici le 'kill' de l'exécutable) sans bloquer l’application principale
-Pas d'utilisation de librairies complexes comme WX ou QT, mais uniquement les ressources de bases IHM/GUI Windows via l'excellent wrapper Python for Windows extensions (PyWin32). On aurait pu directement utiliser la librairie windll.user32 via ctypes (moins pythonic)
Vous pouvez retrouvez le code via GitHub.
Dans la suite du billet, le code LimitTask.py et quelques informations pour créer une application windows avec le module py2exe.
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 LimitTask module is a simple command to limit one specific windows processus (task) for a certain time during a slot of time (subscription).
6 LimitTask est un module python pour limiter l'usage d'un process windows dans le temps sur une plage de temps déterminé (abonnement).
7
8 Usage =
9 python.exe LimitTask.pyw <name_process/task.exe> <max use time> <abonnement/plage de temps>
10
11 @author: Python4D/damien.samain@python4d.com
12 @version: 0.1 bêta - Utilisation du module wmi - Log via module logging fichier LMLog
13 @version: 0.2 bêta - Abandon du module wmi utilisation des commandes windows tasklist et taskkill - Sauvegarde PlageTime et TaskTime dans un fichier text LTsave.txt
14 @version: 0.3 bêta - Création de boite de dialogue éphémère - abandon de logging
15 @todo: minimum gui, password, save data in registery
16 """
17
18 import time,sys,os,subprocess
19 # Importation des constantes windows (win32con) et wrapper of win32's functions
20 #@note: http://sourceforge.net/projects/pywin32/
21 import win32gui,win32con
22
23
24 class LimitTask(object):
25
26 def LimitTask(self,Task,NbMin=60,Abonnement=60*24):
27
28 def writesavefile():
29 with open("LTsave.txt",'w') as f:
30 f.writelines([str(PlageTime),"\n",str(TaskTime)])
31
32 TaskTime,PlageTime=[0,time.time()]
33 if os.path.isfile("LTsave.txt"):
34 state=15
35 else:
36 state=10
37
38 #TODO: il faudrait reprendre la state machine pour incorporer les deux boucles while en une seule "while true"
39 #TODO: on a séparé la state machine qui controle le temps de tache avec celle de la plage
40 while True: #Boucle infinie gérant la vérification de la plage et tasktime
41 print "WHILE TRUE => state={:d}, tasktime={:4.1f}, plagetime={:4.1f}".format(state,TaskTime,time.time()-PlageTime)
42 while TaskTime<NbMin*60: #boucle (statemachine) gérant le temps d'entrée et sortie dans la tache
43 print "WHILE TaskTime<NbMin*60 => state={:d}, tasktime={:4.1f}, plagetime={:4.1f}".format(state,TaskTime,time.time()-PlageTime)
44 if state==10: # init des flags et Temps
45 self.flag=0 #flag de la tache dThread pour vérifier si on est sortie de la tache
46 self.flag_message=0
47 TaskTime=0
48 PlageTime=time.time()
49 LastTaskTime=0
50 state=20
51 if state==15: # Récupération des infos du fichier de sauvegarde des données PlgeTime et TaskTime
52 self.flag=0 #flag de la tache dThread pour vérifier si on est sortie de la tache
53 self.flag_message=0
54 with open("LTsave.txt",'r') as f:
55 PlageTime,TaskTime=map(float,f.readlines())
56 LastTaskTime=TaskTime
57 state=20
58 elif state==20:
59 process=self.FindPID(Task)
60 if process==[]:
61 writesavefile()
62 time.sleep(1)
63 state=20
64 else:
65 if PlageTime+Abonnement*60<time.time():
66 print u"Remise à zéro au début Task !"
67 state=10 #On reset tout
68 else:
69 print u"Task trouvé! Id={}-Name={}".format(process[0][0],process[0][1])
70 start=time.time()
71 state=50
72 elif state==50:#task présent reprise du calcul du temps
73 process=self.FindPID(process[0][0])
74 if process==[]:
75 self.flag=0
76 print(u"Sortie de {} - Temps total de jeu = {:4.1f} minutes- Temps avant remise à zéro ={:4.1f} minute(s)!".format(Task,TaskTime/60.0,(Abonnement*60+PlageTime-time.time())/60.0))
77 LastTaskTime=TaskTime #récupération du temps déjà écoulé
78 state=20 #on retourne vérifier qu'il n'y a plus de process en cours
79 else:
80 writesavefile()
81 time.sleep(1)
82 TaskTime=LastTaskTime+time.time()-start
83 if PlageTime+Abonnement*60<time.time():
84 print u"Remise à zéro avant la fin de TaskTime !"
85 state=10 #On reset tout
86 #fin de While TaskTime<NbMin*60 => le temps limite et dépassé
87 writesavefile()
88 time.sleep(1)
89 process=self.FindPID(Task)
90 if process==[]: #pas de process en cours
91 if PlageTime+Abonnement*60<time.time():
92 print u"Remise à zéro en dehors du jeu!\n"
93 TaskTime=0 #permet de rerentrer dans la state machine
94 state=10
95 else:
96 if not self.flag_message==2:
97 self.MessageBoxTimeout(0, u"Tu dois quitter le jeu maintenant - Tu as dépassé les {:4.1f} minute(s) !".format(NbMin), "LimitTask - QUOTA DEPASSE !",4096,10)
98 subprocess.check_output('taskkill /F /PID '+str(process[0][1]))
99 self.flag_message=2
100 else:
101 subprocess.check_output('taskkill /F /PID '+str(process[0][1]))
102 self.MessageBoxTimeout(0, u"Tu as dépassé tes {:4.0f} minute(s) de jeu,\n tu dois attendre encore {:4.1f} minute(s) pour rejouer !".format(NbMin,(Abonnement*60+PlageTime-time.time())/60.0), "LimitTask - QUOTA DEPASSE !", 4096,10)
103
104 def FindPID(self,exename):
105 p=subprocess.Popen('tasklist /FI "IMAGENAME eq '+exename+'"',stdout=subprocess.PIPE,stderr=subprocess.PIPE,creationflags=subprocess.SW_HIDE,shell=True)
106 a=p.stdout.readlines()
107 info=[]
108 i=0
109 thispid=str(os.getpid())
110 while len(a)>3+i and a[3+i].split()!=[]:
111 info.append(a[3+i].split())
112 i+=1
113 info=filter(lambda i:i[1]!=thispid,info)
114 return (info)
115 def MessageBoxTimeout(self,parent,title,message,options=win32con.MB_SYSTEMMODAL,timeout=10):
116 """
117 Création d'une boite de dialogue (#32770 class windows)
118 Attente d'un timeout
119 Fermeture de la boite de dialogue
120 """
121 from threading import Thread as Process
122 _p=Process(target=win32gui.MessageBox, args=(parent,title,message,options))
123 _p.start()
124 time.sleep(timeout)
125 hwnd=win32gui.FindWindow(32770,u"LimitTask - QUOTA DEPASSE !")
126 win32gui.PostMessage(hwnd,win32con.WM_CLOSE)
127
128 if __name__=="__main__":
129 if len(sys.argv)!=4:
130 win32gui.MessageBox(0,
131 u"""Attention ! il y 3 arguments à cette commande:
132
133 1) 'nom de la tache' task présente dans la liste des processus windows
134 2) Temps utilisation max. de l'exécutable - tps de jeu (minutes)
135 3) Temps du renouvelement de l'abonnement - tps remise à zéro (minutes)
136
137 Exemple (pour limiter la l'utilisation de la calculatrice à 10 mn toutes les 60mn):
138
139 >>{} calc.exe 10 60""".format(sys.argv[0].split(os.path.sep)[-1:][0][:-4]), u"Erreur de lancement !",4096) #récupération du dernier élément du fullpath [-1:] d'une liste [0], sans les 4 derniers caractères
140 elif int(sys.argv[2])>=int(sys.argv[3]) :
141 win32gui.MessageBox(0, u"Attention la Plage de remise à zéro doit être Supérieure au temps de la tache/jeu!", u"LimitTask - Erreur de lancement !",4096)
142 else:
143 #TODO: vérifier qu'un autre LimitTask.exe n'existe pas avec la même tache
144 MyPC=LimitTask()
145 while True:
146 process=MyPC.FindPID(os.path.split(sys.argv[0])[1].split('.')[0]+'.exe')
147 if process==[]: break
148 subprocess.check_output('taskkill /F /PID '+str(process[0][1]))
149 MyPC.LimitTask(sys.argv[1],int(sys.argv[2]),int(sys.argv[3]))
150
Pour créer une application autonome:
On utilise le module et script py2exe qui va permettre de créer une application (.exe). Module non standard, il faut donc l'installer.
Pour lancer la création de la distribution [c-à-d, l'exécutable+librairies windows], on va créer un fichier python qui utilisera la méthode setup du module distutils. Le module distutils est un module standard pour l'installation des modules python et création d'installeur windows mais peut-être aussi utiliser pour lancer un script particulier comme py2exe; on utilise donc cette méthode "setup" du module distutils 'uniquement' pour lancer notre script py2exe et créer un exécutable windows à partir de notre fichier python LimitTask.py.
#Fichier de setup pour LimitTask - LimitTaskSetup.py
from distutils.core import setup
import py2exesetup(windows=['LimitTask.py'], options = {"py2exe": {"compressed": 1, "bundle_files": 1, } })
L'argument principal de la méthode setup est "windows=": il indique que l'on veut utiliser une application de type windows plutot que du type "console=" afin de ne pas voir la console python apparaître au lancement de notre application.
L'utilisation des options "compressed" et "bundles_files" permet de ne créer qu'un fichier zip utile pour notre exécutable, où sera rassemblé tous les fichiers nécessaires dont la dll de l'interpréteur python: python27.dll.
L'utilisation des options "compressed" et "bundles_files" permet de ne créer qu'un fichier zip utile pour notre exécutable, où sera rassemblé tous les fichiers nécessaires dont la dll de l'interpréteur python: python27.dll.
Pour obtenir l'exécutable il suffit donc de lancer ce fichier de setup, LimitTaskSetup.py, avec comme argument le script py2exe.
python LimitTaskSetup.py py2exe
Après l'exécution de la commande ci dessus, un directory dist sera créé avec trois fichiers: l'exécutable LimitTask.exe, library.zip et w9xopen.exe. Ce dernier fichier n'est pas utile si vous utilisez windows xp ou supérieur.
Afin de passer les arguments à notre exécutable LimitTask.exe, créez un raccourci.
Vous pouvez alors mettre ce raccourci dans les taches à exécuter au démarrage windows.
a pu algorithme !
RépondreSupprimer