/************************************************************************** * * This is the infamous GTK-SERVER. The purpose of this program is to enable * access to graphical GTK widgets by using shell scripts. Currently these * scriptlanguages have been tested successfully: * * -KSH (stdin) * -ZSH (stdin) * -AWK (stdin, tcp) * -Scriptbasic (tcp) * * Please read the documentation on how to use this program. * Many thanks to the following persons for their help: * * Norman Deppenbroek, Albert Wessel * * (c) Peter van Eerten, september 2003, emphroon@yahoo.com * * Released under the well-known GPL license. * * TODO * - Better errorhandling * - Add more callback signal types * - Support for floating numbers * - Support for Win32 * - Better casting of GTK objects * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /* Define global constants */ #define MAX_LEN 1024 //max length of a STDIN coming from script or configfile /* Define maximum TCP connections */ #define MAX_TCP 16 /* Define callback signals */ #define GTK_SERVER_NONE 0 #define GTK_SERVER_ACTIVATE 1 #define GTK_SERVER_CLICK 2 #define GTK_SERVER_ENTER 3 #define GTK_SERVER_LEAVE 4 #define GTK_SERVER_PRESSED 5 #define GTK_SERVER_RELEASED 6 #define GTK_SERVER_DELETE 7 /* Define current 'callbacked' object */ struct callback { GtkWidget *object; int state; int mousex; int mousey; }; /* Define global instance of current callback */ struct callback Current_Object; /* Define structure containing GTK-SERVER calls */ typedef struct server_struct { char *functionname; int callback; struct server_struct *next; } SERVER; /* Define structure containing configuration data */ typedef struct config_struct { char *name; char *callbacktype; char *returnvalue; char *argamount; char *args[10]; struct config_struct *next; } CONFIG; /* Keep track of te amount of child processes */ int child = 0; //************************************************************************************************* char *Trim_String(char *data) { /* Get rid of whitespaces at the beginning of the string */ while (*data == ' '|| *data == '\t' || *data == '\n') data++; if (strlen(data) == 0) data = ""; /* Get rid of whitespaces at the end of the string */ while (*(data + strlen(data) - 1) == '\n' || *(data + strlen(data) - 1) == ' ' || *(data + strlen(data) - 1) == '\t') *(data + strlen(data) - 1) = '\0'; if (strlen(data) == 0) data = ""; /* Return stripped string */ return(data); } //************************************************************************************************* void sig_handler(int signal) { int status; /* Wait for child without blocking */ if (waitpid(-1, &status, WNOHANG) < 0) return; /* A child process was killed */ else { child--; /* No child processes left? Exit server */ if (!child) exit(0); } } //************************************************************************************************* void delete_callback(GtkWidget *widget, gpointer data) { gtk_widget_hide(GTK_WIDGET(widget)); } //************************************************************************************************* void click_callback(GtkWidget *widget, gpointer data) { Current_Object.object = widget; Current_Object.state = GTK_SERVER_CLICK; Current_Object.mousex = 0; Current_Object.mousey = 0; } //************************************************************************************************* void activate_callback(GtkWidget *widget, gpointer data) { Current_Object.object = widget; Current_Object.state = GTK_SERVER_ACTIVATE; Current_Object.mousex = 0; Current_Object.mousey = 0; } //************************************************************************************************* void Void_Gtk(void *handle, char *name, char *amount, void *arg[], char *buffer) { char *error; // Needed for DL lib errors void (*func)(); // In case of non-returning value /* Search it in GTK library */ func = dlsym(handle, name); if ((error = dlerror()) != NULL) { fprintf (stderr, "ERROR: GTK call not found in library \n%s\n", error); exit(-1); } /* Jump to right amount of arguments */ switch(atoi(amount)){ case 0: // GTK API call has 0 arguments (*func)(); break; case 1: // GTK API call has 1 argument (*func)(arg[0]); break; case 2: // GTK API call has 2 arguments (*func)(arg[0], arg[1]); break; case 3: // GTK API call has 3 arguments (*func)(arg[0], arg[1], arg[2]); break; case 4: // GTK API call has 4 arguments (*func)(arg[0], arg[1], arg[2], arg[3]); break; case 5: // GTK API call has 5 arguments (*func)(arg[0], arg[1], arg[2], arg[3], arg[4]); break; case 6: // GTK API call has 6 arguments (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]); break; case 7: // GTK API call has 7 arguments (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]); break; case 8: // GTK API call has 8 arguments (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]); break; default: printf("ERROR: Too many arguments"); exit (-1); } /* Return positive response */ strcpy(buffer, "ok"); return; } //************************************************************************************************* void Widget_Gtk(void *handle, char *name, char *amount, char *type, void *arg[], char *buffer) { char *error; // Needed for DL lib errors GtkWidget *(*func)(); // In case function returns a string GtkWidget *widget; // Temporary widget holder /* Search it in GTK library */ func = dlsym(handle, name); if ((error = dlerror()) != NULL) { fprintf (stderr, "ERROR: GTK call not found in library \n%s\n", error); exit(-1); } /* Jump to right amount of arguments */ switch(atoi(amount)){ case 0: // GTK API call has 0 arguments widget = (*func)(); break; case 1: // GTK API call has 1 argument widget = (*func)(arg[0]); break; case 2: // GTK API call has 2 arguments widget = (*func)(arg[0], arg[1]); break; case 3: // GTK API call has 3 arguments widget = (*func)(arg[0], arg[1], arg[2]); break; case 4: // GTK API call has 4 arguments widget = (*func)(arg[0], arg[1], arg[2], arg[3]); break; case 5: // GTK API call has 5 arguments widget = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4]); break; case 6: // GTK API call has 6 arguments widget = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]); break; case 7: // GTK API call has 7 arguments widget = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]); break; case 8: // GTK API call has 8 arguments widget = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]); break; default: fprintf(stderr, "ERROR: Too many arguments\n"); exit (-1); } /* Connect signals */ if (!strcmp(type,"DELETE")) gtk_signal_connect(GTK_OBJECT(widget), "delete-event", GTK_SIGNAL_FUNC(delete_callback), NULL); if (!strcmp(type,"CLICK")) g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK (click_callback), NULL); if (!strcmp(type,"ACTIVATE")) gtk_signal_connect(GTK_OBJECT(widget), "activate", GTK_SIGNAL_FUNC(activate_callback), NULL); /* Convert to long value */ sprintf(buffer, "%d", (int)(widget)); /* Return positive response */ return; } //************************************************************************************************* void String_Gtk(void *handle, char *name, char *amount, void *arg[], char *buffer) { char *error; // Needed for DL lib errors char *(*func)(); // In case function returns a string /* Search it in GTK library */ func = dlsym(handle, name); if ((error = dlerror()) != NULL) { fprintf (stderr, "ERROR: GTK call not found in library \n%s\n", error); exit(-1); } /* Jump to right amount of arguments */ switch(atoi(amount)){ case 0: // GTK API call has 0 arguments strcpy(buffer, (*func)()); break; case 1: // GTK API call has 1 argument strcpy(buffer, (*func)(arg[0])); break; case 2: // GTK API call has 2 arguments strcpy(buffer, (*func)(arg[0], arg[1])); break; case 3: // GTK API call has 3 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2])); break; case 4: // GTK API call has 4 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2], arg[3])); break; case 5: // GTK API call has 5 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2], arg[3], arg[4])); break; case 6: // GTK API call has 6 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5])); break; case 7: // GTK API call has 7 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6])); break; case 8: // GTK API call has 8 arguments strcpy(buffer, (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7])); break; default: fprintf(stderr, "ERROR: Too many arguments\n"); exit (-1); } /* Return result */ return; } //************************************************************************************************* void Bool_Gtk(void *handle, char *name, char *amount, void *arg[], char *buffer) { char *error; // Needed for DL lib errors gboolean *(*func)(); // In case function returns a float gboolean *boolean; // Temporary float holder /* Search function in GTK library */ func = dlsym(handle, name); if ((error = dlerror()) != NULL) { fprintf (stderr, "ERROR: GTK call not found in library \n%s\n", error); exit(-1); } /* Jump to right amount of arguments */ switch(atoi(amount)){ case 0: // GTK API call has 0 arguments boolean = (*func)(); break; case 1: // GTK API call has 1 argument boolean = (*func)(arg[0]); break; case 2: // GTK API call has 2 arguments boolean = (*func)(arg[0], arg[1]); break; case 3: // GTK API call has 3 arguments boolean = (*func)(arg[0], arg[1], arg[2]); break; case 4: // GTK API call has 4 arguments boolean = (*func)(arg[0], arg[1], arg[2], arg[3]); break; case 5: // GTK API call has 5 arguments boolean = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4]); break; case 6: // GTK API call has 6 arguments boolean = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]); break; case 7: // GTK API call has 7 arguments boolean = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]); break; case 8: // GTK API call has 8 arguments boolean = (*func)(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]); break; default: fprintf(stderr, "ERROR: Too many arguments\n"); exit (-1); } // Convert to float value if (boolean) strcpy(buffer, "1"); else strcpy(buffer, "0"); // Return result return; } //************************************************************************************************* void Call_Realize (char *inputdata, SERVER *Find_Server, CONFIG *Find_Config, void *handle, char *retstr) { char *gtk_api_call; char *arg; // Temporary argument void *tmparg[10]; // Array needed for arguments of the API call int i; if (strlen(inputdata) > 0) { //Get the API call part gtk_api_call = Trim_String(strtok(inputdata, "(")); //Check GTK_SERVER callbacks while ((Find_Server != NULL) && (strcmp(Find_Server->functionname, gtk_api_call))){ Find_Server = Find_Server->next; } //Have we found a GTK-SERVER call? if (Find_Server != NULL) { //Yes, find the first argument if ((arg = strtok(NULL, ")")) != NULL){ //Find out the callback state for the given object if ((long)Current_Object.object == (long)(atol(arg)) && (long)Current_Object.state == Find_Server->callback) { Current_Object.state = GTK_SERVER_NONE; strcpy(retstr, "1"); return; } //Return 0 if there is no callback state strcpy(retstr,"0"); return; } //GTK-SERVER call found, but no argument given else { printf("ERROR: Missing callback object in API call\n"); exit(-1); } } //No GTK-SERVER call, so check for a genuine GTK call else { //Check GTK_CONFIG list while ((Find_Config != NULL) && (strcmp(Find_Config->name, gtk_api_call))) { Find_Config = Find_Config->next; } //Have we found a GTK call? if (Find_Config != NULL) { //Now get the arguments-------------------->also add FLOAT switch(atoi(Find_Config->argamount)){ case 0: break; case 1: //Get argument if((arg = strtok(NULL, ")")) != NULL) { if (!strcmp(Find_Config->args[0], "LONG")) tmparg[0] = (long*)atol(arg); if (!strncmp(Find_Config->args[0], "WIDGET", 6)) tmparg[0] = (GtkWidget*)(atol(arg)); if (!strcmp(Find_Config->args[0], "STRING")) tmparg[0] = (char*)arg; //if (!strcmp(Find_Config->args[0], "FLOAT")) tmparg[0] = (double*)atof(arg); if (!strcmp(Find_Config->args[0], "NULL")) tmparg[0] = NULL; } break; default: //Get the first argument(s) for (i = 0; i < atoi(Find_Config->argamount) - 1; i++) { if((arg = Trim_String(strtok(NULL, ","))) != NULL) { if (!strcmp(Find_Config->args[i], "LONG")) tmparg[i] = (long*)atol(arg); if (!strncmp(Find_Config->args[i], "WIDGET", 6)) tmparg[i] = (GtkWidget*)(atol(arg)); if (!strcmp(Find_Config->args[i], "STRING")) tmparg[i] = (char*)arg; if (!strcmp(Find_Config->args[i], "NULL")) tmparg[i] = NULL; } } //Get last argument of the row if((arg = Trim_String(strtok(NULL, ")"))) != NULL) { if (!strcmp(Find_Config->args[i], "LONG")) tmparg[i] = (long*)atol(arg); if (!strncmp(Find_Config->args[i], "WIDGET", 6)) tmparg[i] = (GtkWidget*)(atol(arg)); if (!strcmp(Find_Config->args[i], "STRING")) tmparg[i] = (char*)arg; if (!strcmp(Find_Config->args[i], "NULL")) tmparg[i] = NULL; } break; } //The GTK CALL has no return value if (!strcmp(Find_Config->returnvalue, "NONE")){ Void_Gtk(handle, Find_Config->name, Find_Config->argamount, tmparg, retstr); } //The GTK CALL has a widget return value else if (!strcmp(Find_Config->returnvalue, "WIDGET")){ Widget_Gtk(handle, Find_Config->name, Find_Config->argamount, Find_Config->callbacktype, tmparg, retstr); } //The GTK CALL delivers a string as a returnvalue else if (!strcmp(Find_Config->returnvalue, "STRING")){ String_Gtk(handle, Find_Config->name, Find_Config->argamount, tmparg, retstr); } /* The GTK CALL delivers a boolean as a returnvalue */ else if (!strcmp(Find_Config->returnvalue, "BOOL")) Bool_Gtk(handle, Find_Config->name, Find_Config->argamount, tmparg, retstr); /* Other return value (future expansion) */ else{ } } //API call not found else strcpy(retstr,"-1"); } } return; } //************************************************************************************************* //************************************************************* This is the main loop of the server //************************************************************************************************* int main(int argc, char *argv[]) { /* Define list for GTK-SERVER calls */ SERVER *Gtk_Server_Api; SERVER *Start_Gtk_Server_Api; /* Define list for CONFIGURATION settings */ CONFIG *Gtk_Api_Config; CONFIG *Start_Gtk_Api_Config; CONFIG *Conf_Last; /* Define the configfile */ FILE *configfile; /* Define the line input, declare memory */ char *line = (char*)malloc(MAX_LEN*sizeof(char)); /* Count current line number of configfile */ int count_line; /* Define handle to GTK library */ void *handle; /* Define temp variable for loops */ int i; /* This pointer contains the string which is returned to the client program */ char *retstr; /* Define vars for TCP sockets */ char *host; char *port; int sockfd; int yes = 1; struct hostent *he; struct sockaddr_in my_addr; // my address information int sin_size, new_fd; struct sockaddr_in their_addr; // connector's address information int numbytes; char buf[MAX_LEN]; // Buffer containing data from socket /* SIGNAL struct for child processes */ struct sigaction sa; /* Initialize GTK SERVER struct */ Gtk_Server_Api = (SERVER*)malloc(sizeof(SERVER)); Start_Gtk_Server_Api = Gtk_Server_Api; /* Fill in gtk-server values */ Gtk_Server_Api->functionname = "gtk_server_click_event"; Gtk_Server_Api->callback = GTK_SERVER_CLICK; Gtk_Server_Api->next = (SERVER*)malloc(sizeof(SERVER)); Gtk_Server_Api = Gtk_Server_Api->next; Gtk_Server_Api->functionname = "gtk_server_activate_event"; Gtk_Server_Api->callback = GTK_SERVER_ACTIVATE; Gtk_Server_Api->next = (SERVER*)malloc(sizeof(SERVER)); Gtk_Server_Api = Gtk_Server_Api->next; Gtk_Server_Api->functionname = "gtk_server_delete_event"; Gtk_Server_Api->callback = GTK_SERVER_DELETE; Gtk_Server_Api->next = NULL; /* Open the config file */ configfile = fopen ("gtk-server.cfg", "r"); if (configfile == NULL){ printf("ERROR: Cannot find config file!\n"); exit(-1); } count_line = 0; /* Claim memory for configfile entrys */ Gtk_Api_Config = (CONFIG*)malloc(sizeof(CONFIG)); Start_Gtk_Api_Config = Gtk_Api_Config; /* Extract the entry's of the configfile into the array */ while (fgets (line, MAX_LEN, configfile) != NULL){ count_line++; /* Check if the line is a comment */ if (strncmp(line, "#", 1)) { /* No, split the record */ Gtk_Api_Config->name = strtok(line, ","); if (Gtk_Api_Config->name != NULL) { /* Get rid of surrounding whitespaces */ Gtk_Api_Config->name = Trim_String(Gtk_Api_Config->name); /* Get next term: callback signal type */ if ((Gtk_Api_Config->callbacktype = strtok(NULL, ",")) != NULL) Gtk_Api_Config->callbacktype = Trim_String(Gtk_Api_Config->callbacktype); else { printf("ERROR: Missing callbacktype in GTK config file at line %d\n", count_line); exit(-1); } /* Get next term: return value */ if ((Gtk_Api_Config->returnvalue = strtok(NULL, ",")) != NULL) Gtk_Api_Config->returnvalue = Trim_String(Gtk_Api_Config->returnvalue); else { printf("ERROR: Missing returnvalue in GTK config file at line %d\n", count_line); exit(-1); } /* Get next term: amount of arguments */ if ((Gtk_Api_Config->argamount = strtok(NULL, ",")) != NULL) Gtk_Api_Config->argamount = Trim_String(Gtk_Api_Config->argamount); else { printf("ERROR: Missing argumentamount in GTK config file at line %d\n", count_line); exit(-1); } /* Get the separate arguments */ for (i = 0; i < atoi(Gtk_Api_Config->argamount); i++){ if((Gtk_Api_Config->args[i] = strtok(NULL, ",")) != NULL) Gtk_Api_Config->args[i] = Trim_String(Gtk_Api_Config->args[i]); } } /* Declare next item */ Conf_Last = Gtk_Api_Config; Gtk_Api_Config->next = (CONFIG*)malloc(sizeof(CONFIG)); Gtk_Api_Config = Gtk_Api_Config->next; Gtk_Api_Config->next = NULL; /* Claim memory for next config entry */ line = (char*)malloc(MAX_LEN*sizeof(char)); } } /* No records left, define end point */ Conf_Last->next = NULL; /* Open the GTK library */ handle = dlopen("libgtk-x11-2.0.so", RTLD_LAZY||RTLD_GLOBAL); if (!handle){ printf("ERROR: The GTK library could not be opened.\n"); fputs(dlerror(), stderr); exit(-1); } /* Run by STDIN if there are no arguments */ if (argc < 2) { printf("\n*** This is the GTK-SERVER version 0.2 ***\n\n"); printf("Usage:"); printf("\tgtk-server = this help\n\n"); printf("\tgtk-server stdin = start server in STDIN/STDOUT mode\n"); printf("\tgtk-server = start server in TCP mode\n"); } /* There is an argument, check on HELP flag */ else if (!strcmp(argv[1], "stdin")) { /* This is the main input loop via STDIN */ while (1) { fgets (line, MAX_LEN, stdin); Call_Realize(Trim_String(line), Start_Gtk_Server_Api, Start_Gtk_Api_Config, handle, retstr); printf("%s\n", retstr); fflush(stdout); } } /* Else consider this to be TCP socket - host:port */ else { if (strstr(argv[1], ":") == NULL){ printf("ERROR: Argument format must be 'host:port'.\n"); exit(-1); } host = Trim_String(strtok(argv[1], ":")); port = Trim_String(strtok(NULL, ":")); sockfd = 0; if ((he=gethostbyname(host)) == NULL) { // get the host info printf ("\nSpecified host does not exist!\n"); printf("System message: %s\n",strerror(errno)); exit(-1); } else if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("\nCannot connect to socket!\n"); printf("System message: %s\n",strerror(errno)); exit(-1); } else if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { printf("\nCannot configure socket!\n"); printf("System message: %s\n",strerror(errno)); exit(-1); } /* Perform BIND to the host:port */ else { my_addr.sin_family = AF_INET; my_addr.sin_port = htons((long)atol(port)); // Fill in specified port my_addr.sin_addr = *((struct in_addr *)he->h_addr); // Fill in specified IP address memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { printf("Unable to bind the specified socket address!\n"); printf("System message: %s\n",strerror(errno)); exit(-1); } else if (listen(sockfd, MAX_TCP) == -1) { printf("Unable to listen to the specified socket address!\n"); printf("System message: %s\n",strerror(errno)); exit(-1); } } /* Register zombie process reaper */ sa.sa_handler = sig_handler; /* Do not block other signals */ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) < 0) { printf("Could not set signal handler!\n"); perror("sigaction"); exit(-1); } /* Now accept incoming connections */ while (1){ sin_size = sizeof(struct sockaddr_in); if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1){ perror("accept"); continue; } /* We are in the child process */ if (!fork()) { /* Close the listener socket */ close (sockfd); /* Claim memory for returnvalue */ retstr = (char*)malloc(sizeof(char)); /* We enter the mainloop - read incoming text */ while(1){ if ((numbytes = recv(new_fd, buf, MAX_LEN - 1, 0)) > 0){ buf[numbytes] = '\0'; Call_Realize(Trim_String((char*)buf), Start_Gtk_Server_Api, Start_Gtk_Api_Config, handle, retstr); /* Now send the result back to the socket */ strcat(retstr, "\n"); send(new_fd, retstr, strlen(retstr), 0); } } } /* This is the parent process */ else { /* Close the new formed socket, the parent process does not need it */ close (new_fd); child++; } } } return 0; } /* //Show entry's from configfile Gtk_Api_Config = Start_Gtk_Api_Config; while (Gtk_Api_Config != NULL){ printf("%s", Gtk_Api_Config->name); printf(" - %s", Gtk_Api_Config->callbacktype); printf(" - %s", Gtk_Api_Config->returnvalue); printf(" - %s", Gtk_Api_Config->argamount); for (i = 0; i < atoi(Gtk_Api_Config->argamount); i++){ printf(" - %s", Gtk_Api_Config->args[i]); } printf("\n"); Gtk_Api_Config = Gtk_Api_Config->next; } printf ("Adres: %d\n", func); (*func)(NULL, NULL); Define logfile for debugging purposes FILE *log; log = fopen ("log.txt", "a"); fprintf(log, "%s\n",Find_Config->name); fprintf(log, "%d\n", widget); fflush(log); fclose(log); exit (1); */