Search This Blog

Wednesday, June 11, 2014

Screen Locking with X and C

I had an issue recently with screen locking not working properly in LTSP.  The shadow file is not available on the file system of the thick client, so when the screen lock is run as a local program, the user can not unlock to get back to the desktop.

The work around was to install xscreensaver, which authenticates using a PAM module when the screen is locked.  However, the 'hook' which tells the system to 'lock' after a determined idle time is disabled to prevent gnome from locking out the user.

So, I created my own C application which runs in the background and monitors for user mouse movement or keystrokes.  There are 4 threads:


  • Timer Thread (Track user idle time, locks screen if threshold is reached.)
  • Screen Watch Thread (Start timer if screen is ever Unlocked)
  • Mouse Thread (Restart timer if mouse is moved)
  • Keyboard Thread (Restart timer if keystroke is made)

I used several sources and although this is not the most elegant solution it does work.  I would appreciate any feed back, as this is the first C application I have ever written, except for "Hello world."




#include
#include
#include
#include
#include
#include

typedef struct Timer
{
double elapsed;
struct timespec start,finish;
}Timer;


//Predefine global vars.
double timeLimit = 300.0; //seconds
int verboseClock = 0; //1-True,0-False
int verboseMouse = 0; //1-True,0-False
int verboseKeyboard = 0; //1-True,0-False

//do not modify these
volatile int movement = 0;
volatile int screenlocked = 0;
pthread_t threads[4];
int rc_one;
int rc_two;
int rc_three;
int rc_four;


void *MouseWatch(void *threadid) {
//This threa will watch the mouse and ensure the timer is reset if event fired.
FILE *fp;
char buffer[3];
/* Open the command for reading from xinput. */
fp = popen("xinput --test 9", "r");
if (fp == NULL) {
printf("Failed to watch mouse\n");
exit;
}
printf("Watching Mouse...\n");
/* Read the output a line at a time - output it. */
  while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
if(verboseMouse) {  
printf("%c", buffer[0]);
}
    if(buffer[0]=='m') { 
//motion detected.
movement = 1;

}
  }
pclose(fp);
}



void *LockTimer(void *threadid) {
//This thread acts as a timer until Locked, provided mousemove==0 the whole time.
Timer stopwatch;
//start timer.
clock_gettime(CLOCK_MONOTONIC, &stopwatch.start);
//while time < timeLimit seconds.
while(stopwatch.elapsed < timeLimit ) {
if(movement == 1) {
movement = 0;
//mouse moved, restart the clock
clock_gettime(CLOCK_MONOTONIC, &stopwatch.start);
}
//get elapsed time.  if clock has exceeded threshold, lock screen.
       sleep(1);
clock_gettime(CLOCK_MONOTONIC, &stopwatch.finish);
       stopwatch.elapsed = (stopwatch.finish.tv_sec - stopwatch.start.tv_sec);
       stopwatch.elapsed += (stopwatch.finish.tv_nsec - stopwatch.start.tv_nsec) / 1000000000.0;
if(verboseClock) {
printf("elapsed time: %f\n",stopwatch.elapsed);
}
if(screenlocked) { break; }
}
system("xscreensaver-command -lock");
screenlocked = 1;

while(screenlocked) {
sleep(2);
}

printf("restarting the timer\n");
rc_two = pthread_create(&threads[2], NULL, LockTimer, (void *)2);
if(rc_two)  {
printf("ERROR; return code from pthread_create() is %d\n", rc_two);
                exit(-1);
}

pthread_exit(NULL);
}

void *KeyboardWatch(void *threadid) {
//This threa will watch the keyboard and ensure the timer is reset if key is pressed.
FILE *fp;
char buffer[3];
/* Open the command for reading from xinput. */
fp = popen("xinput --test 10", "r");
if (fp == NULL) {
printf("Failed to watch keyboard\n");
exit;
}
printf("Watching Keyboard...\n");
/* Read the output a line at a time - output it. */
  while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
    if(verboseKeyboard) { printf("%s", buffer); }
    if(buffer[0]=='k') { 
//key press detected.
movement = 1;
}
  }
pclose(fp);
}

void *ScreenWatch(void *threadid) {
//This thread will watch the screen and ensure timer is started when screen is 'U'nlocked.
FILE *fp;
char buffer[50];
/* Open the command for reading. */
  fp = popen("xscreensaver-command -watch", "r");
  if (fp == NULL) {
    printf("Failed to run command\n" );
    exit;
  }
printf("Watching Screen...\n");
  /* Read the output a line at a time - output it. */
  while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
   
printf("%s", buffer);

    if(buffer[0]=='U') { 
screenlocked=0;

if(buffer[0]=='L') {
screenlocked=1;
}
  }
}



int main()
{
        printf("In main: creating threads. \n");
//start mouse watch thread
rc_one = pthread_create(&threads[1], NULL, MouseWatch, (void *)1);
        if(rc_one)  {
                        printf("ERROR; return code from mouse thread is %d\n", rc_one);
                        exit(-1);
                }
//start timer thread
        rc_two = pthread_create(&threads[2], NULL, LockTimer, (void *)2);
if(rc_two)  {
                        printf("ERROR; return code from timer thread is %d\n", rc_two);
                        exit(-1);
                }
//start Screen watch thread
rc_three = pthread_create(&threads[3], NULL, ScreenWatch, (void *)3);
        if(rc_three)  {
                        printf("ERROR; return code from screen watch thread is %d\n", rc_one);
                        exit(-1);
                }
//start keyboard watch thread
rc_four = pthread_create(&threads[4], NULL, KeyboardWatch, (void *)4);
        if(rc_four)  {
                        printf("ERROR; return code from keyboard thread is %d\n", rc_four);
                        exit(-1);
                }
//LOOP Forever until user Kills main thread. (Ctrl+C)
while(1) {
sleep(10);
}

}