Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: irate/plugin/lircremote/LircRemoteControlPlugin.java


1   // Copyright 2003 Stephen Blackheath
2   
3   package irate.plugin.lircremote;
4   
5   import irate.plugin.*;
6   import java.util.Enumeration;
7   import java.util.Hashtable;
8   import java.util.List;
9   import java.util.Vector;
10  import java.net.Socket;
11  import java.io.*;
12  import nanoxml.*;
13  
14  /**
15   * Plugin for remote control based on lirc (Linux/Unix).
16   *
17   * @author Stephen Blackheath
18   */
19  public class LircRemoteControlPlugin
20    extends Plugin
21    implements LircRemoteControlListener
22  {
23    private String host;
24    private int port;
25    private Vector functions;
26  
27    public LircRemoteControlPlugin()
28    {
29      host = "localhost";
30      port = 8765;
31      functions = new Vector();
32      functions.add(new Function() {
33        public String getID() {return "this-sux";}
34        public String getName() {return "Rate as 'This sux'";}
35        public void perform() {getApp().setRating(getApp().getSelectedTrack(), 0); getApp().skip();}
36      });
37      functions.add(new Function() {
38        public String getID() {return "yawn";}
39        public String getName() {return "Rate as 'Yawn'";}
40        public void perform() {getApp().setRating(getApp().getSelectedTrack(), 2);}
41      });
42      functions.add(new Function() {
43        public String getID() {return "not-bad";}
44        public String getName() {return "Rate as 'Not bad'";}
45        public void perform() {getApp().setRating(getApp().getSelectedTrack(), 5);}
46      });
47      functions.add(new Function() {
48        public String getID() {return "cool";}
49        public String getName() {return "Rate as 'Cool'";}
50        public void perform() {getApp().setRating(getApp().getSelectedTrack(), 7);}
51      });
52      functions.add(new Function() {
53        public String getID() {return "love-it";}
54        public String getName() {return "Rate as 'Love it'";}
55        public void perform() {getApp().setRating(getApp().getSelectedTrack(), 10);}
56      });
57      functions.add(new Function() {
58        public String getID() {return "pause/resume";}
59        public String getName() {return "Pause/Resume";}
60        public void perform() {getApp().setPaused(!getApp().isPaused());}
61      });
62      functions.add(new Function() {
63        public String getID() {return "skip";}
64        public String getName() {return "Skip";}
65        public void perform() {getApp().skip();}
66      });
67    }
68  
69    public List getFunctions()
70    {
71      return functions;
72    }
73  
74    /**
75     * Get a short identifier for this Plugin.
76     */
77    public String getIdentifier()
78    {
79      return "lirc-remote";
80    }
81  
82    /**
83     * Get a short description of this plugin.
84     */
85    public String getDescription()
86    {
87      return "lirc remote control (Linux/Unix)";
88    }
89  
90  
91  // ------ Listeners ----------------------------------------------------
92  
93    private Vector listeners = new Vector();
94  
95    public void addLircRemoteControlListener(LircRemoteControlListener listener)
96    {
97      listeners.add(listener);
98      connect();
99    }
100 
101   public void removeLircRemoteControlListener(LircRemoteControlListener listener)
102   {
103     if (listeners.size() == 1 && listeners.contains(listener))
104         disconnect();
105     listeners.remove(listener);
106   }
107 
108   private void notifyConnectStatusChanged()
109   {
110     Vector listeners = (Vector) this.listeners.clone();
111     for (int i = 0; i < listeners.size(); i++) {
112       LircRemoteControlListener listener = (LircRemoteControlListener) listeners.get(i);
113       listener.connectStatusChanged(this, connectStatus);
114     }
115   }
116 
117   private void notifyButtonPressed(Button button)
118   {
119     Vector listeners = (Vector) this.listeners.clone();
120     for (int i = 0; i < listeners.size(); i++) {
121       LircRemoteControlListener listener = (LircRemoteControlListener) listeners.get(i);
122       listener.buttonPressed(this, button);
123     }
124   }
125 
126 
127 // ------ I/O ----------------------------------------------------------
128 
129   private boolean terminating;
130   private Object timer = new Object();
131   private IOThread ioThread;
132   private Socket s;
133   private BufferedReader r;
134 
135   /**
136    * Subclasses to override to do real work of attaching.
137    * Application is available through getApp().
138    */
139   protected synchronized void doAttach()
140   {
141     addLircRemoteControlListener(this);
142   }
143 
144   /**
145    * Subclasses to override to do real work of detaching.
146    * Application is available through getApp().
147    */
148   protected synchronized void doDetach()
149   {
150     removeLircRemoteControlListener(this);
151   }
152 
153   /**
154    * True if we are attempting to connect.
155    */
156   private boolean connected = false;
157   private boolean isConnected() {return connected;}
158 
159   /**
160    * True if we have actually established a connection.
161    */
162   private boolean connectStatus = false;
163 
164   /**
165    * Return true if we have established a connection to the lirc daemon.
166    */
167   public boolean getConnectStatus() {return connectStatus;}
168 
169   private void connect()
170   {
171     if (!connected) {
172       connected = true;
173       terminating = false;
174       ioThread = new IOThread();
175       ioThread.start();
176     }
177   }
178 
179   private void disconnect()
180   {
181     if (connected) {
182       try {
183         if (ioThread != null) {
184           ioThread.terminating = true;
185             // Wake up the timer if it happens to be doing a timed wait.
186           synchronized (timer) {
187             timer.notifyAll();
188           }
189     Socket s = ioThread.s;
190           if (s != null)
191             try {s.close();} catch (IOException e) {}
192     // Work around GCJ bug:  Closing the socket, as we do above, does not
193     // terminate the thread under GCJ, so Thread.join() deadlocks.
194           // Instead, we just leave it dangling. This works out not so bad,
195           // because I've written the code in such a way as to make it clean the
196           // dangling thread up the next time a remote control button is pressed. 
197     //try {ioThread.join();} catch (InterruptedException e) {}
198         }
199       }
200       finally {
201         ioThread = null;
202         connected = false;
203   connectStatus = false;
204   notifyConnectStatusChanged();
205       }
206     }
207   }
208 
209   public String getHost()
210   {
211     return host;
212   }
213 
214   public synchronized void setHost(String host)
215   {
216     if (!this.host.equals(host)) {
217       this.host = host;
218       if (isConnected()) {
219   disconnect();
220   connect();
221       }
222     }
223   }
224 
225   public int getPort()
226   {
227     return port;
228   }
229 
230   public synchronized void setPort(int port)
231   {
232     if (this.port != port) {
233       this.port = port;
234       if (isConnected()) {
235   disconnect();
236   connect();
237       }
238     }
239   }
240 
241   public class IOThread
242     extends Thread
243   {
244     IOThread()
245     {
246     }
247 
248     public Socket s;
249     public boolean terminating = false;
250 
251     public void run()
252     {
253       while (!terminating) {
254   long connectStart = System.currentTimeMillis();
255   try {
256     s = new Socket(host, port);
257       // This makes the thread wake up occasionally, which will allow any
258       // dead threads to be cleaned up.  This can result from disconnect()
259       // being called at a bad time.
260             // Note that setSoTimeout does NOT work on GCJ (version 3.0).
261     s.setSoTimeout(10000);
262     try {
263       BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream()));
264       try {
265         connectStatus = true;
266         notifyConnectStatusChanged();
267         while (!terminating) {
268     try {
269       String buttonText = r.readLine();
270       if (terminating || buttonText == null)
271         break;
272       int space1 = buttonText.indexOf(' ');
273       if (space1 >= 0) {
274         int space2 = buttonText.indexOf(' ', space1+1);
275         String repeatCountStr = buttonText.substring(space1+1, space2);
276         String idStr = buttonText.substring(space2+1);
277         Button button = new Button(idStr, Integer.parseInt(repeatCountStr));
278         notifyButtonPressed(button);
279       }
280     }
281     catch (InterruptedIOException e) {
282     }
283               }
284       }
285       finally {
286         if (!terminating) {
287     connectStatus = false;
288     notifyConnectStatusChanged();
289               }
290         try {r.close();} catch (IOException e) {}
291       }
292     }
293     finally {
294       try {s.close();} catch (IOException e) {}
295       s = null;
296     }
297   }
298   catch (NumberFormatException e) {
299       e.printStackTrace();
300   }
301   catch (IOException e) {
302   }
303   if (terminating)
304       break;
305     // If the failed connection took less than 20 seconds, then wait the remainder
306     // of the 20 seconds.  This protects against getting stuck in a tight loop. 
307   long duration = System.currentTimeMillis() - connectStart;
308   if (duration < 20000L && duration >= 0L) {
309     try {
310       synchronized (timer) {
311         timer.wait(20000L - duration);
312       }
313     }
314     catch (InterruptedException e) {}
315   }
316       }
317     }
318   }
319 
320   /*
321   public static void main(String[] args)
322   {
323     LircRemoteControlPlugin plugin = new LircRemoteControlPlugin();
324     plugin.setHost("tui");
325     plugin.addLircRemoteControlListener(new LircRemoteControlListener() {
326       public void connectStatusChanged(LircRemoteControlPlugin plugin, boolean connected)
327       {
328   System.out.println(connected?"** Connected":"** Disconnected");
329       }
330 
331       public void buttonPressed(LircRemoteControlPlugin plugin, LircRemoteControlPlugin.Button button)
332       {
333   System.out.println(button);
334       }
335     });
336     plugin.attach(null);
337   }
338   */
339 
340 
341 // ------ Translate button presses to actions --------------------------
342 
343   public void connectStatusChanged(LircRemoteControlPlugin plugin, boolean connected)
344   {
345     System.out.println("lirc remote control: "+(connected?"connected":"disconnected"));
346   }
347 
348   public void buttonPressed(LircRemoteControlPlugin plugin, Button button)
349   {
350     // System.out.println("lirc remote control: "+button);
351     Function function = null;
352     synchronized (this) {
353       outerLoop:
354       for (int i = 0; i < functions.size(); i++) {
355   Function thisFunction = (Function) functions.get(i);
356   for (int j = 0; j < thisFunction.buttons.size(); j++) {
357     Button thisButton = (Button) thisFunction.buttons.get(j);
358     if (thisButton.equals(button)) {
359       function = thisFunction;
360       break outerLoop;
361     }
362   }
363       }
364     }
365     if (function != null) {
366       System.out.println("lirc remote control: "+function.getName());
367       if (getApp() != null)
368   function.perform();
369     }
370     else {
371       if (button.getRepeatCount() == 0)
372         System.out.println("lirc remote control: unmapped button "+button);
373     }
374   }
375 
376 
377 // ------ Saving/load of configuration details -------------------------
378 
379   /**
380    * Parse the configuration stored in the specified element.
381    */
382   public void parseConfig(XMLElement elt)
383   {
384     for (int i = 0; i < functions.size(); i++) {
385       Function func = (Function) functions.get(i);
386       func.clearConfig();
387     }
388 
389     Enumeration enum = elt.enumerateChildren();
390     while (enum.hasMoreElements()) {
391       XMLElement funcElt = (XMLElement) enum.nextElement();
392       if (funcElt.getName().equals("function")) {
393         String id = funcElt.getStringAttribute("id");
394         for (int i = 0; i < functions.size(); i++) {
395           Function func = (Function) functions.get(i);
396           if (func.getID().equals(id))
397             func.parseXML(funcElt);
398         }
399       }
400       else
401       if (funcElt.getName().equals("connect")) {
402         host = funcElt.getStringAttribute("host");
403         port = Integer.parseInt(funcElt.getStringAttribute("port"));
404       }
405     }
406   }
407 
408   /**
409    * Format the configuration of this plugin by modifying the specified
410    * element.
411    */
412   public void formatConfig(XMLElement elt)
413   {
414     XMLElement connElt = new XMLElement(new Hashtable(), false, false);
415     elt.addChild(connElt);
416     connElt.setName("connect");
417     connElt.setAttribute("host", host);
418     connElt.setAttribute("port", Integer.toString(port));
419 
420     for (int i = 0; i < functions.size(); i++) {
421       Function func = (Function) functions.get(i);
422       elt.addChild(func.formatXML());
423     }
424   }
425 }
426