/**
* GmailAssistant 2.0 (2008-09-07)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail and Google Apps email accounts.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeshell.zs.gmailassistant;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.Queue;
import org.freeshell.zs.common.Debug;
/**
* Blink the keyboard LED.
*/
class KeyboardLedBlinker
{
/** interval between keyboard num-lock LED blinking, in milliseconds */
private static final long BLINK_INTERVAL_MILLISECONDS = 200L;
/** number of blinking iterations when performing a test */
private static final int TEST_ITERATIONS = 2;
/** queued actions to be performed by the timer */
private final Queue<ActionType> actions = new ArrayDeque<ActionType>();
/** has the thread for blinking keyboard LED been terminated? */
private volatile boolean threadTerminated = false;
/**
* Constructor.
*
* @param parent
* GmailAssistant parent object
*/
KeyboardLedBlinker(
final GmailAssistant parent)
{
/***********************************************
* INITIALIZE THREAD FOR BLINKING KEYBOARD LED *
***********************************************/
new Thread(new Runnable()
{
/* toolkit for accessing LED state */
private Toolkit toolkit = null;
/* robot for pressing keys */
private Robot robot = null;
/**
* Set the LED to the specified state.
*
* @param state
* new state of the LED
*/
private void setKeyboardLedState(
final boolean state)
{
final String key = parent.properties.getString("alert.led.key");
final int keyCode;
if ("num".equals(key))
{
keyCode = KeyEvent.VK_NUM_LOCK;
}
else if ("caps".equals(key))
{
keyCode = KeyEvent.VK_CAPS_LOCK;
}
else if ("scroll".equals(key))
{
keyCode = KeyEvent.VK_SCROLL_LOCK;
}
else
{
throw new IllegalStateException("Illegal \"Alert LED Key\" selection state.");
}
try
{
toolkit.setLockingKeyState(keyCode, state);
}
catch (Exception e)
{
if (robot != null)
{
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
}
}
}
@Override
public void run()
{
try
{
toolkit = Toolkit.getDefaultToolkit();
}
catch (Exception e)
{
/* ignore */
}
try
{
robot = new Robot();
}
catch (Exception e)
{
/* ignore */
}
/* current LED state */
boolean state = false;
/* saved LED states */
boolean savedNumLockState = false;
boolean savedCapsLockState = false;
boolean savedScrollLockState = false;
/* is LED currently blinking? */
boolean blinking = false;
while (true)
{
final ActionType action;
synchronized (actions)
{
action = actions.poll();
}
if (action == ActionType.START)
{
if (!blinking)
{
savedNumLockState = getLockingKeyState(toolkit, KeyEvent.VK_NUM_LOCK);
savedCapsLockState = getLockingKeyState(toolkit, KeyEvent.VK_CAPS_LOCK);
savedScrollLockState = getLockingKeyState(toolkit, KeyEvent.VK_SCROLL_LOCK);
state = false;
blinking = true;
}
}
else if (action == ActionType.STOP)
{
if (blinking)
{
setLockingKeyState(toolkit, KeyEvent.VK_NUM_LOCK, savedNumLockState);
setLockingKeyState(toolkit, KeyEvent.VK_CAPS_LOCK, savedCapsLockState);
setLockingKeyState(toolkit, KeyEvent.VK_SCROLL_LOCK, savedScrollLockState);
blinking = false;
}
}
else if (action == ActionType.TEST)
{
final boolean existingNumLockState = getLockingKeyState(toolkit, KeyEvent.VK_NUM_LOCK);
final boolean existingCapsLockState = getLockingKeyState(toolkit, KeyEvent.VK_CAPS_LOCK);
final boolean existingScrollLockState = getLockingKeyState(toolkit, KeyEvent.VK_SCROLL_LOCK);
for (int i = 0; i < TEST_ITERATIONS; i++)
{
setKeyboardLedState(true);
Debug.sleep(BLINK_INTERVAL_MILLISECONDS);
setKeyboardLedState(false);
Debug.sleep(BLINK_INTERVAL_MILLISECONDS);
}
setLockingKeyState(toolkit, KeyEvent.VK_NUM_LOCK, existingNumLockState);
setLockingKeyState(toolkit, KeyEvent.VK_CAPS_LOCK, existingCapsLockState);
setLockingKeyState(toolkit, KeyEvent.VK_SCROLL_LOCK, existingScrollLockState);
}
else if (action == ActionType.TERMINATE)
{
threadTerminated = true;
return;
}
if (blinking)
{
state = !state;
setKeyboardLedState(state);
}
Debug.sleep(BLINK_INTERVAL_MILLISECONDS);
}
}
}).start();
}
/**
* Start the blinker, if it has not already been started.
*/
void start()
{
synchronized (actions)
{
actions.add(ActionType.START);
}
}
/**
* Stop the blinker, if it is blinking.
*/
void stop()
{
synchronized (actions)
{
actions.add(ActionType.STOP);
}
}
/**
* Test the blinker.
*/
void test()
{
synchronized (actions)
{
actions.add(ActionType.TEST);
}
}
/**
* Stop the blinker now, if it is blinking.
*/
void cancelAll()
{
synchronized (actions)
{
actions.clear();
actions.add(ActionType.STOP);
}
}
/**
* Terminate the thread for blinking keyboard LED.
* This method blocks until the thread is cancelled.
*/
void terminate()
{
synchronized (actions)
{
actions.clear();
actions.add(ActionType.STOP);
actions.add(ActionType.TERMINATE);
}
while (!threadTerminated)
{
Debug.sleep(BLINK_INTERVAL_MILLISECONDS / 10);
}
}
/**
* Get the state of the specified locking key, without throwing an exception.
*
* @param toolkit
* <code>Toolkit</code> object to be used for accessing the state
* @param keyCode
* key code of the locking key
* @return
* state of the specified locking key; false if an exception occurs when accessing
* the state
*/
private static boolean getLockingKeyState(
final Toolkit toolkit,
final int keyCode)
{
try
{
return toolkit.getLockingKeyState(keyCode);
}
catch (Exception e)
{
return false;
}
}
/**
* Set the state of the specified locking key, without throwing an exception.
*
* @param toolkit
* <code>Toolkit</code> object to be used for accessing the state
* @param keyCode
* key code of the locking key
* @param state
* new state of the specified locking key
*/
private static void setLockingKeyState(
final Toolkit toolkit,
final int keyCode,
final boolean state)
{
try
{
toolkit.setLockingKeyState(keyCode, state);
}
catch (Exception e)
{
/* ignore */
}
}
/******************
* NESTED CLASSES *
******************/
/**
* Types of action.
*/
private enum ActionType
{
START,
STOP,
TEST,
TERMINATE
}
}