2019-04-29 15:42:59 +02:00
/*
This file is part of g15daemon.
g15daemon is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
g15daemon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with g15daemon; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
(c) 2006 Mike Lampard, Philip Lawatsch, and others
2019-04-29 15:43:00 +02:00
$Revision: 299 $ - $Date: 2007-09-09 19:19:52 +0200 (Sun, 09 Sep 2007) $ $Author: mlampard $
2019-04-29 15:42:59 +02:00
This daemon listens on localhost port 15550 for client connections,
and arbitrates LCD display. Allows for multiple simultaneous clients.
Client screens can be cycled through by pressing the 'L1' key.
*/
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libg15.h>
#include <g15daemon.h>
static int leaving ;
int server_events ( plugin_event_t * myevent );
2019-04-29 15:43:00 +02:00
#ifndef SO_PRIORITY
#define SO_PRIORITY 12
#endif
2019-04-29 15:42:59 +02:00
/* tcp server defines */
#define LISTEN_PORT 15550
#define LISTEN_ADDR "127.0.0.1"
/* any more than this number of simultaneous clients will be rejected. */
#define MAX_CLIENTS 10
/* custom plugininfo for clients... */
plugin_info_t lcdclient_info [] = {
/* TYPE, name, initfunc, updatefreq, exitfunc, eventhandler, initfunc */
{ G15_PLUGIN_LCD_SERVER , "LCDclient" , NULL , 500 , NULL , ( void * ) server_events , NULL },
{ G15_PLUGIN_NONE , "" , NULL , 0 , NULL , NULL , NULL }
};
static void process_client_cmds ( lcdnode_t * lcdnode , int sock , unsigned int * msgbuf , unsigned int len )
{
switch ( msgbuf [ 0 ]){
case CLIENT_CMD_SWITCH_PRIORITIES : {
g15daemon_send_event ( lcdnode , G15_EVENT_REQ_PRIORITY , 1 );
break ;
}
case CLIENT_CMD_IS_FOREGROUND : { /* client wants to know if it's currently viewable */
pthread_mutex_lock ( & lcdlist_mutex );
memset ( msgbuf , 0 , 2 );
if ( lcdnode -> list -> current == lcdnode ){
msgbuf [ 0 ] = '1' ;
} else {
msgbuf [ 0 ] = '0' ;
}
pthread_mutex_unlock ( & lcdlist_mutex );
send ( sock , msgbuf , 1 , MSG_OOB );
break ;
}
case CLIENT_CMD_IS_USER_SELECTED : { /* client wants to know if it was set to foreground by the user */
pthread_mutex_lock ( & lcdlist_mutex );
if ( lcdnode -> lcd -> usr_foreground ) /* user manually selected this lcd */
msgbuf [ 0 ] = '1' ;
else
msgbuf [ 0 ] = '0' ;
pthread_mutex_unlock ( & lcdlist_mutex );
send ( sock , msgbuf , 1 , 0 );
break ;
}
default :
if ( msgbuf [ 0 ] & CLIENT_CMD_MKEY_LIGHTS )
{ /* client wants to change the M-key backlights */
lcdnode -> lcd -> mkey_state = msgbuf [ 0 ] - 0x20 ;
lcdnode -> lcd -> state_changed = 1 ;
//if the client is the keyhandler, allow full, direct control over the mled status
if ( lcdnode -> lcd -> masterlist -> remote_keyhandler_sock == lcdnode -> lcd -> connection )
setLEDs ( msgbuf [ 0 ] - 0x20 );
} else if ( msgbuf [ 0 ] & CLIENT_CMD_KEY_HANDLER )
{
g15daemon_log ( LOG_WARNING , "Client is taking over keystate" );
lcdnode -> list -> remote_keyhandler_sock = sock ;
g15daemon_log ( LOG_WARNING , "Client has taken over keystate" );
}
2019-04-29 15:43:00 +02:00
else if ( msgbuf [ 0 ] & CLIENT_CMD_BACKLIGHT )
{
send ( sock , & lcdnode -> lcd -> backlight_state , 1 , MSG_OOB );
lcdnode -> lcd -> backlight_state = msgbuf [ 0 ] - 0x80 ;
lcdnode -> lcd -> state_changed = 1 ;
}
else if ( msgbuf [ 0 ] & CLIENT_CMD_KB_BACKLIGHT )
{
setKBBrightness (( unsigned int ) msgbuf [ 0 ] - 0x8 );
}
else if ( msgbuf [ 0 ] & CLIENT_CMD_CONTRAST )
{
send ( sock , & lcdnode -> lcd -> contrast_state , 1 , MSG_OOB );
lcdnode -> lcd -> contrast_state = msgbuf [ 0 ] - 0x40 ;
lcdnode -> lcd -> state_changed = 1 ;
}
2019-04-29 15:42:59 +02:00
}
}
/* create and open a socket for listening */
int init_sockserver (){
int listening_socket ;
int yes = 1 ;
int tos = 0x18 ;
struct sockaddr_in servaddr ;
if (( listening_socket = socket ( AF_INET , SOCK_STREAM , 0 )) < 0 ) {
g15daemon_log ( LOG_WARNING , "Unable to create socket. \n " );
return - 1 ;
}
setsockopt ( listening_socket , SOL_SOCKET , SO_REUSEADDR , & yes , sizeof ( yes ));
setsockopt ( listening_socket , SOL_SOCKET , SO_PRIORITY , & tos , sizeof ( tos ));
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
inet_aton ( LISTEN_ADDR , & servaddr . sin_addr );
servaddr . sin_port = htons ( LISTEN_PORT );
if ( bind ( listening_socket , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ) {
g15daemon_log ( LOG_WARNING , "error calling bind() \n " );
return - 1 ;
}
if ( listen ( listening_socket , MAX_CLIENTS ) < 0 ) {
g15daemon_log ( LOG_WARNING , "error calling listen() \n " );
return - 1 ;
}
return listening_socket ;
}
int g15_send ( int sock , char * buf , unsigned int len )
{
int total = 0 ;
int retval = 0 ;
int bytesleft = len ;
struct pollfd pfd [ 1 ];
while ( total < len && ! leaving ) {
memset ( pfd , 0 , sizeof ( pfd ));
pfd [ 0 ]. fd = sock ;
pfd [ 0 ]. events = POLLOUT | POLLERR | POLLHUP | POLLNVAL ;
if ( poll ( pfd , 1 , 500 ) > 0 ) {
if ( pfd [ 0 ]. revents & POLLOUT && ! ( pfd [ 0 ]. revents & POLLERR || pfd [ 0 ]. revents & POLLHUP || pfd [ 0 ]. revents & POLLNVAL )) {
retval = send ( sock , buf + total , bytesleft , 0 );
if ( retval == - 1 ) {
break ;
}
bytesleft -= retval ;
total += retval ;
}
if (( pfd [ 0 ]. revents & POLLERR || pfd [ 0 ]. revents & POLLHUP || pfd [ 0 ]. revents & POLLNVAL )){
retval =- 1 ;
break ;
}
}
}
return retval ==- 1 ?- 1 : 0 ;
}
int g15_recv ( lcdnode_t * lcdnode , int sock , char * buf , unsigned int len )
{
int total = 0 ;
int retval = 0 ;
int msgret = 0 ;
int bytesleft = len ;
struct pollfd pfd [ 1 ];
unsigned int msgbuf [ 20 ];
while ( total < len && ! leaving ) {
memset ( pfd , 0 , sizeof ( pfd ));
pfd [ 0 ]. fd = sock ;
pfd [ 0 ]. events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL ;
if ( poll ( pfd , 1 , 500 ) > 0 ){
if ( pfd [ 0 ]. revents & POLLPRI && ! ( pfd [ 0 ]. revents & POLLERR || pfd [ 0 ]. revents & POLLHUP || pfd [ 0 ]. revents & POLLNVAL )) { /* receive out-of-band request from client and deal with it */
memset ( msgbuf , 0 , 20 );
msgret = recv ( sock , msgbuf , 10 , MSG_OOB );
if ( msgret < 1 ) {
break ;
}
process_client_cmds ( lcdnode , sock , msgbuf , len );
}
else if ( pfd [ 0 ]. revents & POLLIN && ! ( pfd [ 0 ]. revents & POLLERR || pfd [ 0 ]. revents & POLLHUP || pfd [ 0 ]. revents & POLLNVAL )) {
retval = recv ( sock , buf + total , bytesleft , 0 );
if ( retval < 1 ) {
break ;
}
total += retval ;
bytesleft -= retval ;
}
if (( pfd [ 0 ]. revents & POLLERR || pfd [ 0 ]. revents & POLLHUP || pfd [ 0 ]. revents & POLLNVAL )){
retval =- 1 ;
break ;
}
}
}
return total ;
}
/* the client must send 6880 bytes for each lcd screen. This thread will continue to copy data
* into the clients LCD buffer for as long as the connection remains open.
* so, the client should open a socket, check to ensure that the server is a g15daemon,
* and send multiple 6880 byte packets (1 for each screen update)
* once the client disconnects by closing the socket, the LCD buffer is
* removed and will no longer be displayed.
*/
void * lcd_client_thread ( void * display ) {
lcdnode_t * g15node = display ;
lcd_t * client_lcd = g15node -> lcd ;
int retval ;
unsigned int width , height , buflen , header = 4 ;
int client_sock = client_lcd -> connection ;
char helo [] = SERV_HELO ;
unsigned char * tmpbuf = g15daemon_xmalloc ( 6880 );
if ( g15_send ( client_sock , ( char * ) helo , strlen ( SERV_HELO )) < 0 ){
goto exitthread ;
}
/* check for requested buffer type.. we only handle pixel buffers atm */
if ( g15_recv ( g15node , client_sock ,( char * ) tmpbuf , 4 ) < 4 )
goto exitthread ;
/* we will in the future handle txt buffers gracefully but for now we just hangup */
if ( tmpbuf [ 0 ] == 'G' ) {
while ( ! leaving ) {
retval = g15_recv ( g15node , client_sock ,( char * ) tmpbuf , 6880 );
if ( retval != 6880 ){
break ;
}
pthread_mutex_lock ( & lcdlist_mutex );
memset ( client_lcd -> buf , 0 , 1024 );
g15daemon_convert_buf ( client_lcd , tmpbuf );
client_lcd -> ident = random ();
pthread_mutex_unlock ( & lcdlist_mutex );
}
}
else if ( tmpbuf [ 0 ] == 'R' ) { /* libg15render buffer */
while ( ! leaving ) {
retval = g15_recv ( g15node , client_sock , ( char * ) tmpbuf , 1048 );
if ( retval != 1048 ) {
break ;
}
pthread_mutex_lock ( & lcdlist_mutex );
memcpy ( client_lcd -> buf , tmpbuf , sizeof ( client_lcd -> buf ));
client_lcd -> ident = random ();
pthread_mutex_unlock ( & lcdlist_mutex );
}
}
else if ( tmpbuf [ 0 ] == 'W' ){ /* wbmp buffer - we assume (stupidly) that it's 160 pixels wide */
while ( ! leaving ) {
retval = g15_recv ( g15node , client_sock ,( char * ) tmpbuf , 865 );
if ( ! retval )
break ;
if ( tmpbuf [ 2 ] & 1 ) {
width = (( unsigned char ) tmpbuf [ 2 ] ^ 1 ) | ( unsigned char ) tmpbuf [ 3 ];
height = tmpbuf [ 4 ];
header = 5 ;
} else {
width = tmpbuf [ 2 ];
height = tmpbuf [ 3 ];
header = 4 ;
}
buflen = ( width / 8 ) * height ;
if ( buflen > 860 ){ /* grab the remainder of the image and discard excess bytes */
/* retval=g15_recv(client_sock,(char*)tmpbuf+865,buflen-860); */
retval = g15_recv ( g15node , client_sock , NULL , buflen - 860 );
buflen = 860 ;
}
if ( width != 160 ) /* FIXME - we ought to scale images I suppose */
goto exitthread ;
pthread_mutex_lock ( & lcdlist_mutex );
memcpy ( client_lcd -> buf , tmpbuf + header , buflen + header );
client_lcd -> ident = random ();
pthread_mutex_unlock ( & lcdlist_mutex );
}
}
exitthread :
if ( client_lcd -> masterlist -> remote_keyhandler_sock == client_sock )
client_lcd -> masterlist -> remote_keyhandler_sock = 0 ;
close ( client_sock );
free ( tmpbuf );
g15daemon_lcdnode_remove ( display );
pthread_exit ( NULL );
}
/* poll the listening socket for connections, spawning new threads as needed to handle clients */
int g15_clientconnect ( g15daemon_t ** g15daemon , int listening_socket ) {
int conn_s ;
struct pollfd pfd [ 1 ];
pthread_t client_connection ;
pthread_attr_t attr ;
lcdnode_t * clientnode ;
memset ( pfd , 0 , sizeof ( pfd ));
pfd [ 0 ]. fd = listening_socket ;
pfd [ 0 ]. events = POLLIN ;
if ( poll ( pfd , 1 , 500 ) > 0 ){
if ( ! ( pfd [ 0 ]. revents & POLLIN )){
return 0 ;
}
if ( ( conn_s = accept ( listening_socket , NULL , NULL ) ) < 0 ) {
if ( errno == EWOULDBLOCK || errno == EAGAIN ){
} else {
g15daemon_log ( LOG_WARNING , "error calling accept() \n " );
return - 1 ;
}
}
clientnode = g15daemon_lcdnode_add ( g15daemon );
clientnode -> lcd -> connection = conn_s ;
/* override the default (generic handler and use our own for our clients */
clientnode -> lcd -> g15plugin -> info = ( void * )( & lcdclient_info );
memset ( & attr , 0 , sizeof ( pthread_attr_t ));
pthread_attr_init ( & attr );
pthread_attr_setdetachstate ( & attr , PTHREAD_CREATE_DETACHED );
pthread_attr_setstacksize ( & attr , 256 * 1024 ); /* set stack to 768k - dont need 8Mb - this is probably rather excessive also */
if ( pthread_create ( & client_connection , & attr , lcd_client_thread , clientnode ) != 0 ) {
g15daemon_log ( LOG_WARNING , "Unable to create client thread." );
if ( close ( conn_s ) < 0 ) {
g15daemon_log ( LOG_WARNING , "error calling close() \n " );
return - 1 ;
}
}
pthread_detach ( client_connection );
}
return 0 ;
}
/* this thread only listens for new connections.
* sockserver_accept will spawn a new thread for each connected client
*/
static void lcdserver_thread ( void * lcdlist ){
g15daemon_t * masterlist = ( g15daemon_t * ) lcdlist ;
int g15_socket =- 1 ;
if (( g15_socket = init_sockserver ()) < 0 ){
g15daemon_log ( LOG_ERR , "Unable to initialise the server at port %i" , LISTEN_PORT );
return ;
}
if ( fcntl ( g15_socket , F_SETFL , O_NONBLOCK ) < 0 ) {
g15daemon_log ( LOG_ERR , "Unable to set socket to nonblocking" );
}
while ( ! leaving ) {
g15_clientconnect ( & masterlist , g15_socket );
}
close ( g15_socket );
return ;
}
/* incoming events */
int server_events ( plugin_event_t * event ) {
lcd_t * lcd = ( lcd_t * ) event -> lcd ;
switch ( event -> event )
{
case G15_EVENT_KEYPRESS :{
if ( lcd -> connection && lcd -> masterlist -> remote_keyhandler_sock != lcd -> connection ) { /* server client */
if (( send ( lcd -> connection ,( void * ) & event -> value , sizeof ( event -> value ), 0 )) < 0 )
g15daemon_log ( LOG_WARNING , "Error in send: %s \n " , strerror ( errno ));
}
break ;
}
case G15_EVENT_VISIBILITY_CHANGED :
break ;
case G15_EVENT_USER_FOREGROUND :
lcd -> usr_foreground = event -> value ;
break ;
case G15_EVENT_MLED :
case G15_EVENT_BACKLIGHT :
case G15_EVENT_CONTRAST :
/* should never receive these */
default :
break ;
}
return G15_PLUGIN_OK ;
}
/* if no exitfunc or eventhandler, member should be NULL */
plugin_info_t g15plugin_info [] = {
/* TYPE, name, initfunc, updatefreq, exitfunc, eventhandler, initfunc */
{ G15_PLUGIN_LCD_SERVER , "LCDServer" , ( void * ) lcdserver_thread , 500 , NULL , ( void * ) server_events , NULL },
{ G15_PLUGIN_NONE , "" , NULL , 0 , NULL , NULL , NULL }
};