Growl UDP notification Klasse
Growl UDP notification class
Growl is a well known tool for the Mac (and now for other platforms as well), which displays small notification messages on your screen. Any program can register itself as a source of messages and then send notifications. Additionally, Growl can listen to a UDP port for the same purpose. The PHP class in this article uses this remote messaging protocol for notifying local or remote computers. The Growl protocol is not really complex, but here a short overview how "UDP-Growling" works:
- Growl wants to know which program sent an incoming notification and which type of notification was received (a warning, error, new email, new Skype message ...). This allows you to set up what to do with these messages in your Growl configuration panel.
- Hence, there are two major message types in the UDP protocol: Registration and Notification. The registration packets register a new application and specify which types of notification are sent from this application. The notification messages contain the messages that you want to display.
- The notification messages contain title, description, priority, and if they are sticky (keep being displayed until you click them)
- For security reasons, Growl has a password protection for UDP remote registrations
Risk a glance at the sample source code so see how it works:
Growl ist ein bekanntes Tool für den Mac (und jetzt auch für andere Betriebssysteme), das kleine Mitteilungen auf dem Bildschirm anzeigt. Jedes beliebige Programm kann sich als Quelle von diesen "Notifications" registrieren und dann Mitteilungen an Growl senden. Zusätzlich hört das Tool auf einem UDP-Port auf Netzwerknachrichten. Die hier beschriebene PHP Klasse verwendet genau dieses Remote-Protokoll, um Mitteilungen (lokal oder an andere Computer) abzuschicken. Das Gowl-Protokoll ist nicht sehr komplex, aber dennoch möchte ich hier ein paar einführende Grundlagen zusammenfassen:
- Growl will wissen, welches Programm eine einkommende Message gesendet hat und welchem Typs diese Message ist (Warnung, Fehler, neue eMail, Skype-Message usw.). Dadurch kann in den Programmeinstellungen von Growl festgelegt werden, wie diese Messages angezeigt werden sollen.
- Folglich gibt es zwei Haupttypen im Netzwerk-Protokoll: Registrierung und Mitteilung. Dabei melden Registrierungspakete ein neues Programm an und spezifizieren, welche Mitteilungstypen damit gesendet werden. Die Mitteilungspakete beinhalten die Messages selbst.
- Die Mitteilungspakete bestehen aus den Daten: Titel, Beschreibung, Priorität, und ob die Message "sticky" ist, d.h. ob sie solange angezeigt wird, bis der Nutzer sie wegklickt.
- Aus Sicherheitsgründen kann in den Growl-Einstellungen ein Passwort für Programmregistrierungen eingestellt werden
Einfach mal einen Blick auf das Anwendungsbeispiel werfen, dann wird alles klar :)
Sample source code
Anwendungsbeispiel
<?php
require_once(dirname("GrowlNotifier.class.php");
print '<html><body><pre>';
// Sets defaults
GrowlNotifier::config(array(
'server' => '127.0.0.1', // default server ip (this computer)
'password' => '', // default password
'application' => 'PhpGrowler', // default application identifier
'register' => true, // send registrations?
'notifications' => array( // defaule registered notifications
'message','warning', 'error', // default enabled notifications
'disabled notify' => false, // explicity disabled notification
'enabled notify' => 'true' // explicity enabled notification
)
));
// Instance using default config
$growl = new GrowlNotifier();
$growl->notify('Hello Growl', "This is a message for you");
// Instance using config specified in constructor
$growl = new GrowlNotifier('localhost', '');
$growl->notify('Hello localhost', "This is another message for you");
print '</pre></body></html>';
?>
Output
To see the output of this example "out of the box", go to your Growl settings pane under "Network", and tick the check boxes "Listen for incoming notifications" and "Allow remote application registration". Leave the password field blank. Well - I assume that Growl is installed and your Apache server is running on your local machine. Probably a system message "Allow incoming network transfers for GrowlHelper" will be displayed - the best is to say "always allow".
Ausgabe
Um die Ausgabe dieses Beispiels "out of the box" zu sehen, öffne Deine Growl-Einstellungen unter "Netzwerk" und setze die Checkboxen "Listen for incoming notifications" und "Allow remote application registration". Ok - Ich gehe mal davon aus, dass Growl schon installiert ist und der Apache-Server auf deinem Computer läuft. Vielleicht wird eine System-Message "Eingehenden Netzwerktransfer für "GrowlHelper" erlauben" angezeigt - das Beste wird wohl die Antwort "Immer erlauben" sein.
Class source code
Klassen-Quelltext
<?php
/**
* Growl notification class
* @package de.atwillys.php.swLib
* @version 1.0
* @author Stefan Wilhelm, 2010
* @license GPL
*/
class GrowlNotifier {
/**
* Class configuration defaults
* @ststicvar array
*/
private static $config = array(
'server' => '127.0.0.1', // default server ip
'password' => '', // default password
'application' => 'PhpGrowler', // default application identifier
'register' => true, // send registrations?
'notifications' => array( // defaule registered notifications
'message','warning', 'error', // default enabled notifications
//'something else' => false // disabled notification
)
);
/**
* The version of the protocol
* @const int
*/
const PROTOCOL_VERSION = 1;
/**
* Packacke type registration
* @const int
*/
const TYPE_REGISTRATION = 0;
/**
* Packacke type notification
* @const int
*/
const TYPE_NOTIFICATION = 1;
/**
* Growl UDP port
* @const int
*/
const UDP_PORT = 9887;
/**
* The Growl server/computer IP address
* @var string
*/
private $server = '127.0.0.1';
/**
* The Growl server/computer password
* @var string
*/
private $password = '';
/**
* Application, which is sent to identify the message source
* @var string
*/
private $application = 'PhpGrowler';
/**
* Available notification types
* @var array
*/
private $registeredNotifications = array('message');
/**
* Helper to achieve that the registration is only sent once.
* @var bool
*/
private $hasRegistered = null;
/**
* Class configuration. Sets the specified config settings (merges
* with the existing). Returns the actual configuration after the
* new array has been merged to the defaults/previous settings.
* @param array $config
* @return array
*/
public static final function config($config=array()) {
if(!is_array($config)) {
throw new Exception('GrowlNotifier config is no array');
} else {
self::$config = array_merge(self::$config, $config);
}
return self::$config;
}
/**
* Sends an UDP application registration package to the specified growl
* server/computer. The notifications array can be either associative
* (key is the notification name, value is a boolean value that describes
* that the notification is enabled) or numerical indexed (key is a number,
* value is the notification, enabled by default true). $server is the IP
* address of the server, $password the password of the server, $application
* the name or slug or any string identifier of the application.
* @param string $server
* @param string $password
* @param string $application
* @param array $notifications
*/
private static function sendUdpRegistration($server, $password, $application, array $notifications) {
$application = utf8_encode($application);
$encoded = $defaults = '';
$ne = $nd = 0;
foreach($notifications as $notification => $enabled) {
$enabled = (bool) $enabled;
$notification = utf8_encode(trim($notification));
$encoded .= pack('n', strlen($notification)) . $notification;
$ne++;
if($enabled !== false) { $defaults .= pack('c', $ne-1); $nd++; }
}
$data = pack(
'c2nc2', self::PROTOCOL_VERSION, self::TYPE_REGISTRATION,
strlen($application), $ne, $nd
) . $application . $encoded . $defaults;
$data .= pack('H32', md5($data . trim($password)));
$sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if($sock == false) {
throw new Exception('Sending registration failed: ' . socket_strerror(socket_last_error()));
}
if(@socket_sendto($sock, $data, strlen($data), MSG_EOF, $server, self::UDP_PORT) < 0) {
@socket_close($sock);
throw new Exception('Sending registration failed: ' . socket_strerror(socket_last_error()));
}
@socket_close($sock);
}
/**
* Sends an UDP application notification package to the specified growl
* server/computer. $server is the IP address of the server, $password
* the password of the server, $application the name or slug or any string
* identifier of the application, $notification one of the registered
* notification identifiers - rest of parameters are self explaining.
* @param string $server
* @param string $password
* @param string $application
* @param string $notification
* @param string $title=''
* @param string $description=''
* @param int $priority=0
* @param bool $sticky=false
*/
private static function sendUdpNotification($server, $password, $application, $notification, $title='',
$description='', $priority=0, $sticky=false) {
$application = utf8_encode(trim($application));
$notification = utf8_encode(trim($notification));
$description = utf8_encode(trim($description));
$title = utf8_encode(trim($title));
$priority = intval($priority);
$data = pack('c2n5', self::PROTOCOL_VERSION, self::TYPE_NOTIFICATION,
(2*(intval($priority) & 7)) | (intval($priority) < 0 ? 8 : 0) | ($sticky==true ? 256 : 0),
strlen($notification), strlen($title), strlen($description), strlen($application)
) . $notification . $title . $description . $application;
$data .= pack('H32', md5($data . trim($password)));
$sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if($sock == false) {
throw new Exception('Sending notification failed: ' . socket_strerror(socket_last_error()));
}
if(@socket_sendto($sock, $data, strlen($data), MSG_EOF, $server, self::UDP_PORT) < 0) {
@socket_close($sock);
throw new Exception('Sending notification failed: ' . socket_strerror(socket_last_error()));
}
@socket_close($sock);
}
/**
* Constructor
* @param string $server
* @param string $password
* @param array $registeredNotifications
*/
public function __construct($server=null, $password=null, $application=null, $registeredNotifications=array()) {
$this->setServer(trim($server) != '' ? $server : self::$config['server']);
$this->setPassword(!empty($password) ? $password : self::$config['password']);
$this->setApplication(trim($application) != '' ? $application : self::$config['application']);
$this->setRegisteredNotifications(!empty($registeredNotifications) ? $registeredNotifications : self::$config['notifications']);
}
/**
* Returns the IP address of the server to send registrations/notifications to.
* @return string
*/
public function getServer() {
return $this->server;
}
/**
* Sets the new server IP address
* @param string $server
*/
public function setServer($server) {
$this->server = trim($server);
}
/**
* Returns the password of the server to send registrations/notifications to.
* @return string
*/
public function getPassword() {
return $this->password;
}
/**
* Sets the new server password
* @param string $password
*/
public function setPassword($password) {
$this->password = strval($password);
}
/**
* Returns the application identifier, under which the messages are send.
* @return string
*/
public function getApplication() {
return $this->application;
}
/**
* Sets the new application identified, under which messages are send.
* @param strig $application
*/
public function setApplication($application) {
$this->application = trim($application);
}
/**
* Returns an assoc. array containing the registered notification types
* (or the notifications to register). Keys a the notification names, values
* are the enabled-status (bool).
* @return array
*/
public function getRegisteredNotifications() {
return $this->registeredNotifications;
}
/**
* Sets the new registerd notifications..
* Wants an assoc. array containing the registered notification types
* (or the notifications to register). Keys a the notification names, values
* are the enabled-status (bool). The first registered notification is the
* default one, which will be used if the corresponding argumrnt in notify()
* is empty.
* @param array $notifications
*/
public function setRegisteredNotifications($notifications) {
if(!is_array($notifications)) {
throw new Exception('Notifications to register must be passed as array');
} else if(empty($notifications)) {
throw new Exception('No notifications defined to register');
} else {
$sanatizedNotifications = array();
foreach($notifications as $notification => $enabled) {
if(is_numeric($notification) && !is_bool($enabled)) {
$sanatizedNotifications[trim($enabled)] = true;
} else {
$sanatizedNotifications[trim($notification)] = $enabled;
}
}
$this->registeredNotifications = $sanatizedNotifications;
}
}
/**
* Sends a notification to the configured server.
* @param string $notification
* @param string $title
* @param string $description=''
* @param int $priority=0
* @param bool $sticky=false
*/
public function notify($title, $description='', $notification=null, $priority=0, $sticky=false) {
// Send the registration only once
if($this->hasRegistered !== false) {
if(is_null($this->hasRegistered)) {
$this->hasRegistered = self::$config['register'] ? true : false;
}
if($this->hasRegistered) {
try {
self::sendUdpRegistration($this->getServer(), $this->getPassword(),
$this->getApplication(), $this->getRegisteredNotifications());
$this->hasRegistered = true;
} catch(Exception $e) {
throw new Exception('Notification registration failed:' . $e->getMessage());
}
}
}
// Default is the first registered one ...
if(empty($notification)) {
foreach($this->getRegisteredNotifications() as $notification => $enabled) {
if($enabled) break;
}
}
// send the notification
self::sendUdpNotification($this->getServer(), $this->getPassword(), $this->getApplication(),
$notification, $title, $description, $priority, $sticky);
}
}
?>