Friday, February 25, 2011

How REAL Studio gives you more power with the use of plugins

The REALbasic language excels when you need to go beyond the default options, allowing you access to System and third parties libraries and plugins.


Two years ago or so I wrote a utility to test Apple’s laptops battery autonomy under several processing loads and tasks. Additionally, I also needed the computer “awake” during the tests but didn’t want to change the Energy Preferences Panel settings.


The REALbasic language doesn’t include these kind of classes or properties by default; but we are developers, aren’t we? So I turned my head to the REAL Studio plugins API where I found how truly simple it is to wrap your own functions in C, C++ or Objective-C to access the underlying features of Mac OS X system.


Everything you need is under the Extras > Plugins SDK folder, inside the REAL Studio folder. And you can also find the documentation online at http://docs.realsoftware.com/index.php/REAL_Studio_Plugin_SDK


If you have never written in C or C++ before, you’ll probably find the documentation and the approach to REAL Studio plugins development a bit tough. But if you know how to write programs using such languages, then the provided documentation is enough to get a basic plug-in ready to interact with your REALbasic code in a matter of hours.

In fact that is what I did, finishing the whole project (pluging writting included) in no more than four hours! Below is the code involved, so feel free to use it in your own projects in case you also need to access Apple’s laptop battery information, or to give it a shot of caffeine!
And next time someone tells you something about “basic” in REALbasic language... don’t be fooled!
These are the files you have to write in Apple Xcode for the plugin side (as a module):
file > infobateria.h
#ifndef _inforbateria
#define _inforbateria
typedef struct infoBateria {
SInt32 tiempoCargaCompleta;
SInt32 tiempoRestante;
SInt32 capacidadActual;
SInt32 capacidadMaxima;
SInt32 voltajeActual;
SInt32 amperaje;
SInt32 mAhOriginales;
SInt32 mAhMaximo;
SInt32 mAhActuales;
SInt32 ciclosOriginales;
SInt32 contadorDeCiclos;
SInt32 numeroDeSerie;
char serieBateria[100];
char estadoBateria[100];
char fuenteDeAlimentacion[100];
unsigned char diaDeFabricacion;
unsigned char mesDeFabricacion;
unsigned short anoFabricacion;
SInt32 temperatura;
SInt32 estaCargando;
} infoBateria_t;
#endif
File PMInfoRB.c (or cpp)
#include "rb_plugin.h"
#include
#include
#include
#include
#include
#include
#include
#include "infoBateria.h"
#include "infoBateriaRB.h"
#include
static infoBateria_t *laBateria;
static bool reposoForzado;
static REALstring modeloPortatil;
static IOPMAssertionID assertionID;
io_connect_t root_port;
IONotificationPortRef notifyPortRef;
io_object_t notifierObject;
void* refCon;
void trataNotificaciones( void * refCon, io_service_t service, natural_t messageType, void * messageArgument )
{
switch ( messageType )
{
case kIOMessageCanSystemSleep:
IOCancelPowerChange( root_port, (long)messageArgument );
break;
case kIOMessageSystemWillSleep:
CFShow(CFSTR("Reposo forzado"));
reposoForzado = true;
IOAllowPowerChange( root_port, (long)messageArgument );
break;
case kIOMessageSystemWillPowerOn:
CFShow(CFSTR("Saliendo del reposo"));
reposoForzado = false;
break;
case kIOMessageSystemHasPoweredOn:
break;
default:
break;
}
}
static bool registraNotificacionSleep()
{
IOReturn success = IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep,
kIOPMAssertionLevelOn, &assertionID);
if (success == kIOReturnSuccess)
root_port = IORegisterForSystemPower( refCon, &notifyPortRef, trataNotificaciones, &notifierObject );
if ( root_port == 0 )
{
return false;
}
CFRunLoopAddSource( CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes );
return true;
}
static bool desactivaNotificaciones()
{
IOReturn success = IOPMAssertionRelease(assertionID);
if (success == kIOReturnSuccess)
CFRunLoopRemoveSource( CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes );
IODeregisterForSystemPower( &notifierObject );
IOServiceClose( root_port );
IONotificationPortDestroy( notifyPortRef );
return true;
}
static bool obtenvalores (void) {
CFTypeRef blob;
CFArrayRef array = 0;
CFDictionaryRef diccionario;
size_t buffer = 0;
SInt32 fechaFabricacion, temperatura = 0;
int error;
char nombre[100];
Boolean b;
kern_return_t errorIO = 0;
CFMutableDictionaryRef diccionarioObjeto;
io_registry_entry_t registroIO = 0;
registroIO = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/AppleACPIPlatformExpert/SMB0/AppleECSMBusController/AppleSmartBatteryManager/AppleSmartBattery");
errorIO = IORegistryEntryCreateCFProperties(registroIO, &diccionarioObjeto, kCFAllocatorDefault, 0);
if (!errorIO)
{
if (CFDictionaryContainsKey(diccionarioObjeto, CFSTR("DesignCapacity")))
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("DesignCapacity")), 3, &laBateria->mAhOriginales);
if (CFDictionaryContainsKey(diccionarioObjeto, CFSTR("DesignCycleCount")))
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("DesignCycleCount")), 3, &laBateria->ciclosOriginales);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("ManufactureDate")), 3, &fechaFabricacion);
if (CFDictionaryContainsKey(diccionarioObjeto, CFSTR("SerialNumber")))
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("SerialNumber")), 3, &laBateria->numeroDeSerie);
b = CFStringGetCString((CFStringRef) CFDictionaryGetValue(diccionarioObjeto,CFSTR("BatterySerialNumber")), laBateria->serieBateria, 255, kCFStringEncodingUTF8);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("Temperature")), 3, &temperatura);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("MaxCapacity")), 3, &laBateria->mAhMaximo);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("Voltage")), 3, &laBateria->voltajeActual);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("CurrentCapacity")), 3, &laBateria->mAhActuales);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("CycleCount")), 3, &laBateria->contadorDeCiclos);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionarioObjeto, CFSTR("Amperage")), 3, &laBateria->amperaje);
laBateria->diaDeFabricacion = fechaFabricacion & 31;
laBateria->mesDeFabricacion = (fechaFabricacion & 480) >> 5;
laBateria->anoFabricacion = ((fechaFabricacion & 65024) >> 9) + 1980;
laBateria->temperatura = (( temperatura & 255 ) - 32) / 1.8;
CFRelease(diccionarioObjeto);
error = sysctlbyname("hw.model", NULL, &buffer, NULL, 0);
error = sysctlbyname("hw.model", nombre, &buffer, NULL, 0);
modeloPortatil = REALBuildString(nombre, strlen(nombre));
blob = IOPSCopyPowerSourcesInfo();
if (blob != NULL)
{
array = IOPSCopyPowerSourcesList(blob);
if (array != NULL)
{
diccionario = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(array, 0));
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionario, CFSTR(kIOPSTimeToEmptyKey)), 3, &laBateria->tiempoRestante);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionario,CFSTR(kIOPSCurrentCapacityKey)), 3, &laBateria->capacidadActual);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionario, CFSTR(kIOPSMaxCapacityKey)), 3, &laBateria->capacidadMaxima);
b = CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(diccionario, CFSTR(kIOPSTimeToFullChargeKey)), 3, &laBateria->tiempoCargaCompleta);
b = CFStringGetCString((CFStringRef) CFDictionaryGetValue(diccionario,CFSTR(kIOPSPowerSourceStateKey)), laBateria->fuenteDeAlimentacion, 100, kCFStringEncodingUTF8);
laBateria->fuenteDeAlimentacion[strlen(CFStringGetCStringPtr((CFStringRef) CFDictionaryGetValue(diccionario, CFSTR(kIOPSPowerSourceStateKey)), kCFStringEncodingUTF8)) + 1] = '0';
b = CFStringGetCString((CFStringRef) CFDictionaryGetValue(diccionario,CFSTR("BatteryHealth")), laBateria->estadoBateria, 100, kCFStringEncodingUTF8);
laBateria->estaCargando = CFBooleanGetValue((CFBooleanRef) CFDictionaryGetValue(diccionario, CFSTR(kIOPSIsChargingKey)));
}
else {
return false;
}
}
else {
return false;
}
CFRelease(array);
CFRelease(blob);
}
else {
return false;
}
return true;
}
static bool refrescaValores (void)
{
if (laBateria)
{
return obtenvalores();
}else{
return false;
}
}
static bool getInformacionBateria(infoBateria_t *estructura)
{
laBateria = estructura;
return obtenvalores();
}
static bool getReposoForzado(void)
{
return reposoForzado;
}
static REALstring getModeloPortatil(void)
{
REALLockString(modeloPortatil);
return modeloPortatil;
}
REALproperty PMInfoPropiedades[] = {
{"","reposoForzado","Boolean", REALScopeGlobal | REALconsoleSafe, (REALproc)getReposoForzado,nil},
{"","modeloPortatil","String", REALScopeGlobal | REALconsoleSafe, (REALproc)getModeloPortatil,nil},
};
REALstructure infoBateriaDef[] = {
{ "infoBateria",REALScopeGlobal,infoBateriaRBFields,20},
};
REALmethodDefinition PMInfoMethods[] = {
{ (REALproc)getInformacionBateria, REALnoImplementation, "getInformacionBateria(ByRef prueba as infoBateria) as Boolean", REALconsoleSafe },
{ (REALproc)refrescaValores, REALnoImplementation, "refrescaValores() as Boolean", REALconsoleSafe },
{ (REALproc)registraNotificacionSleep, REALnoImplementation, "registraNotificacionSleep() as Boolean", REALconsoleSafe },
{ (REALproc)desactivaNotificaciones, REALnoImplementation, "desactivaNotificaciones() as Boolean", REALconsoleSafe },
};
REALmoduleDefinition PMInfoModule = {
kCurrentREALControlVersion,
"PMInfoModule",
PMInfoMethods, // Metodos
sizeof(PMInfoMethods) / sizeof(REALmethodDefinition), // numero de m├ętodos
nil, // constantes
0, // contador de constante
PMInfoPropiedades, // propiedades
sizeof(PMInfoPropiedades) / sizeof(REALproperty), // contador de propiedades
infoBateriaDef, //estructuras
sizeof( infoBateriaDef ) / sizeof( REALstructure ), //contador de estructuras
nil,
0,
};
void PluginEntry( void )
{
REALRegisterModule( &PMInfoModule );
}
This is the way you can access the plug-in from your REAL Studio project:
In the App properties, declare estructura as infoBateria


And write the following code in the App Open Event to feed the module data:
dim b as Boolean
b = PMInfoModule.registraNotificacionSleep
if b = false then
MsgBox ("Error registering for receiving notifications")
end
b = PMInfoModule.getInformacionBateria(estructuraBateria)
if b = False then
MsgBox ("Error feeding battery data")
end


Javier Rodriguez provides technical and customer support for REAL Studio in Spanish. A long-time REAL Studio developer, Javier hosts http://www.realbasichispano.es/  a REAL Studio devoted website with tips, tutorials and a forum in Spanish.