Cet article explique comment détecter l’inactivité de l’utilisateur. Il s’applique aussi bien au développement pour Mac OS X que pour iPhone / iPad.
I/O Kit
Par inactivité, il faut comprendre absence d’interaction de l’utilisateur avec la machine. L’interaction peut être le déplacement de la souris, une action sur le clavier, etc, mais non une action déclenchée uniquement par la machine.
Il s’agit d’une collection de frameworks, librairies et outils destinés principalement au développement de drivers pour des composants matériels.
Nous allons donc, pour des raison pratiques, et pour permettre une utilisation générique, créer une classe Objective-C encapsulant ce code C parfois un peu moins digeste pour certains développeurs Cocoa et iPhone.
Configuration du projet
En effet, puisqu’il s’agit d’une librairie, celle-ci doit être «linkée» à notre application finale.

Pour iPhone et iPad, ce framework n’est pas disponible dans son ensemble. Dans un tel cas, il faudra choisir « libIOKit.dylib ».



Utilisation d’IOKitLib
#include <IOKit/IOKitLib.h>
@interface IdleTime: NSObject
{
mach_port_t ioPort;
io_iterator_t ioIterator;
io_object_t ioObject;
}
@property( readonly ) uint64_t timeIdle;
@property( readonly ) NSUInteger secondsIdle;
@end
Les types correspondants à ces variables sont définis dans le fichier « IOKit/IOKitLib.h », que nous incluons.
#include "IdleTime.h"
@implementation IdleTime
- ( id )init
{
if( ( self = [ super init ] ) ) {
}
return self;
}
- ( void )dealloc
{
[ super dealloc ];
}
- ( uint64_t )timeIdle
{
return 0;
}
- ( NSUInteger )secondsIdle
{
uint64_t time;
time = self.timeIdle;
return ( NSUInteger )( time >> 30 );
}
@end
- ( id )init
{
kern_return_t status;
if( ( self = [ super init ] ) ) {
}
return self;
}
La suite de code se passe à l’intérieur de l’instruction « if »:
status = IOMasterPort( MACH_PORT_NULL, &ioPort );
if( status != KERN_SUCCESS ) {
/* Error management... */
}
Nous récupérons dans le code suivant un itérateur sur les services I/O Kit, qui nous servira à accéder à IOHID.
status = IOServiceGetMatchingServices(
ioPort,
IOServiceMatching( "IOHIDSystem" ),
&ioIterator
);
ioObject = IOIteratorNext( ioIterator );
if ( ioObject == 0 ) {
/* Error management */
}
IOObjectRetain( ioObject );
IOObjectRetain( ioIterator );
Il ne faut donc pas oublier d’effectuer un « release » dans la méthode « dealloc »:
- ( void )dealloc
{
IOObjectRelease( ioObject );
IOObjectRelease( ioIterator );
[ super dealloc ];
}
Il ne nous reste plus qu’à interroger ce service, dans la méthode « timeIdle ».
- ( uint64_t )timeIdle
{
kern_return_t status;
CFTypeRef idle;
CFTypeID type;
uint64_t time;
CFMutableDictionaryRef properties;
properties = NULL;
status = IORegistryEntryCreateCFProperties(
ioObject,
&properties,
kCFAllocatorDefault,
0
);
Nous récupérons également toujours un status du kernel, qu’il nous faut vérifier comme précédemment.
idle = CFDictionaryGetValue( properties, CFSTR( "HIDIdleTime" ) );
if( !idle ) {
CFRelease( ( CFTypeRef )properties );
/* Error management */
}
type = CFGetTypeID( idle );
if( type == CFDataGetTypeID() ) {
CFDataGetBytes( ( CFDataRef )idle, CFRangeMake( 0, sizeof( time ) ), ( UInt8 * )&time );
} else if( type == CFNumberGetTypeID() ) {
CFNumberGetValue( ( CFNumberRef )idle, kCFNumberSInt64Type, &time );
} else {
CFRelease( idle );
CFRelease( ( CFTypeRef )properties );
/* Error management */
}
CFRelease( idle );
CFRelease( ( CFTypeRef )properties );
return time;
Demo
gcc -Wall -framework Cocoa -framework IOKit -o idle idle.m && ./idle
2 comments
Excellent article, comme toujours! Bravo!
PS: Ça fait tellement de bien de lire du code aussi bien formatté (surtout pour de l’Objective-C) 😉
Merci d’avoir publié ce tutoriel. Je l’ai étudie afin d’utiliser dans une application que je suis en train d’écrire. Tout fonctionne bien, mais j’ai découvert quelques choses au sujet de ton code que j’aimerais partager.
D’abord, on peut se passer entièrement de l’ouverture du Master Port en spécifiant ‘kIOMasterPortDefault’ au lieu de la variable ‘ioPort’. Puis, ce n’est pas la peine d’utiliser ‘IOServiceGetMatchingServices’ pour obtenir une collection de services. On peut dire tout simplement ‘IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(« IOHIDSystem »))’ pour obtenir une référence au service HID directement. On n’a pas besoin de l’iterator, alors.
En suite, je suggère ‘IORegistryEntryCreateCFProperty(ioService, CFSTR(« HIDIdleTime »), kCFAllocatorDefault, NULL)’ pour se passer d’un dictionnaire et accéder à la variable en question directement.
À mon avis, comme ça le code sera beaucoup plus efficace et clair. Si tu as de commentaires à propos de mes suggestions, je les aimerais savoir.
Encore une fois, merci et excuses-moi de mon français imparfait.
Serge.