/**************************************************************************/
/*
/* LIRCMap.java -- Part of the org.lirc.util package
/* Copyright (C) 2001 Bjorn Bringert (bjorn@mumblebee.com)
/*
/* This program 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.
/*
/* 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, write to the Free Software
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/*
/**************************************************************************/
package org.lirc.util;
import org.lirc.LIRCClient;
import org.lirc.LIRCEvent;
import java.util.*;
import java.io.*;
/** Maps remote control buttons to Strings.
The mapping can be saved to and read from a file with the following format:
(a subset of the .lircrc file format, see the LIRC documentation):
<pre>
# comment
begin
button = button_name
[remote = remote_name]
[repeat = positive integer]
[config = string]
[config = string]
...
end
</pre>
<p>Empty lines and lines starting with "#" are ignored.</p>
<p><code>button</code> is the name of the button that this section concerns.</p>
<p><code>remote</code> is the name of the remote control that this section concerns.
If this line is omitted all remotes will be matched.</p>
<p><code>repeat</code> specifies how repeat events will be matched. A value of 0
means that no repeat events will be matched, i.e. only the first event is matched when
a button is held down. A value n > 0 means that every n:th event is reported, i.e.
every event for which <code>repeat % n == 0</code>. If the repeat line is omitted
only the first event will be reported, i.e. repeat = 0</p>
<p><code>config</code> lines specify the strings that will be mapped to the event. If
several config strings are specified they will be cycled through. This lets
you implement toggle buttons etc.
</p>
Example:
<pre>
begin
button = power
config = on
config = off
end
begin
button = vol+
repeat = 1
config = volup
end
begin
button = four
remote = RM-D90
repeat = 5
config = left
end
</pre>
@version $Revision: 1.7 $
@author Bjorn Bringert (bjorn@mumblebee.com)
*/
public class LIRCMap {
/** The default repeat value. */
private static final int DEFAULT_REPEAT = 0;
/** Name of the program that we handle ir stuff for. */
private String program;
/** List of Entry objects. */
private List list = new ArrayList();
/** Creates a new empty LIRCMap. */
public LIRCMap() {}
/** Creates a new empty LIRCMap. */
public LIRCMap(String program) {
this.program = program;
}
/** Reads a LIRCMap from a file.
Same as:
<pre>
LIRCMap map = new LIRCMap(program);
map.load(file);
</pre>
*/
public LIRCMap(String program, File file) throws IOException {
this(program);
load(file);
}
/** Loads settings from a file. */
public void load(String filename) throws IOException {
load(new File(filename));
}
/** Loads settings from a file. */
public void load(File file) throws IOException {
FileReader reader = new FileReader(file);
load(reader);
reader.close();
}
/** Loads settings from a <code>Reader</code>. */
public void load(Reader reader) throws IOException {
LineNumberReader in = new LineNumberReader(reader);
while (readEntry(in, true)) { }
}
private boolean readEntry(LineNumberReader in, boolean addIt) throws IOException {
while (true) { // find start of entry
String line = in.readLine();
if (line == null) return false; // EOF
line = line.trim();
if (line.equals("") || line.startsWith("#")) { // blank line or comment
// ignore
} else if (line.equals("begin")) {
break; // found start of entry
} else if (line.startsWith("begin")) { // section for some program, ignore it
while (readEntry(in, false)) { } // skip until end of section
} else if (line.startsWith("end")) {
return false; // found end of section
} else {
throw new IOException("Error in input, expected 'begin' on line " + in.getLineNumber());
}
}
String prog = null;
String button = null;
String remote = null;
int repeat = DEFAULT_REPEAT;
List values = new LinkedList();
while (true) {
String line = in.readLine();
if (line == null) throw new IOException("Error in input, expected 'end' on line " + in.getLineNumber()); // EOF
line = line.trim();
if (line.equals("") || line.startsWith("#")) { // blank line or comment
// ignore
} else if (line.equals("end")) {
if (button == null) throw new IOException("Error in input, no 'button' in block ending on line " + in.getLineNumber());
if (addIt && (prog == null || prog.equals(program))) {
add(new Entry(button, remote, repeat, (String[])values.toArray(new String[values.size()])));
}
return true;
} else if (line.startsWith("button")) {
button = readValue(line, in);
} else if (line.startsWith("remote")) {
remote = readValue(line, in);
} else if (line.startsWith("repeat")) {
String val = readValue(line, in);
try {
repeat = Integer.parseInt(val);
} catch (NumberFormatException ex) {
throw new IOException("Error in input '"+line+"' on line " + in.getLineNumber());
}
} else if (line.startsWith("config")) {
values.add(readValue(line, in));
} else if (line.startsWith("prog")) {
prog = readValue(line, in);
} else if (line.startsWith("mode")) {
// ignore
} else if (line.startsWith("flags")) {
// ignore
} else {
//throw new IOException("Error in input '"+line+"' on line " + in.getLineNumber());
System.err.println("Unexpected input '"+line+"' on line " + in.getLineNumber());
}
}
}
private String readValue(String line, LineNumberReader in) throws IOException {
int pos = line.indexOf('=');
if (pos == -1) throw new IOException("Error in input '"+line+"' on line " + in.getLineNumber());
return line.substring(pos+1).trim();
}
/** Writes this LIRCMap to a file. */
public void store(String filename) throws IOException {
store(new File(filename));
}
/** Writes this LIRCMap to a file. */
public void store(File file) throws IOException {
FileWriter out = new FileWriter(file);
store(out);
out.close();
}
/** Writes this LIRCMap to a Writer. */
public void store(Writer writer) throws IOException {
PrintWriter out = new PrintWriter(writer);
Iterator it = list.iterator();
while (it.hasNext()) {
Entry e = (Entry)it.next();
out.println("begin");
out.println("\tbutton = " + e.button);
if (e.remote != null) {
out.println("\tremote = " + e.remote);
}
if (e.repeat != DEFAULT_REPEAT) {
out.println("\trepeat = " + e.repeat);
}
for (int i = 0; i < e.values.length; i++) {
out.println("\tconfig = " + e.values[i]);
}
out.println("end");
}
out.flush();
}
/**
* Gets the String that matches a LIRCEvent.
* @param e The event
* @return A String or null if there is no match
*/
public String get(LIRCEvent e) {
int n = list.size();
for (int i = 0; i < n; i++) {
Entry entry = (Entry)list.get(i);
if (entry.match(e)) {
return entry.nextValue();
}
}
return null;
}
/** Adds a value mapping to this map.
* @param button The button to match
* @param remote The remote value
* @param repeat The repeat value
* @param value The value to map to the parameters
*/
public void add(String button, String remote, int repeat, String value) {
add(new Entry(button, remote, repeat, new String[]{ value }));
}
/** Adds a value mapping to this map.
* @param button The button to match
* @param remote The remote value
* @param repeat The repeat value
* @param values The values to map to the parameters
*/
public void add(String button, String remote, int repeat, String[] values) {
add(new Entry(button, remote, repeat, values));
}
/**
* Adds an Entry.
* @param e The Entry to add
*/
protected void add(Entry e) {
list.add(e);
}
/** The entries in this map. */
protected static class Entry {
protected final String button;
protected final String remote;
protected final int repeat;
protected final String[] values;
private int counter = -1;
/**
* Creates a new Entry
* @param button The button that this entry will match, if null all buttons will be matched
* @param remote The remote that this entry will match, if null all remotes will be matched
* @param repeat The repeat value
*/
public Entry(String button, String remote, int repeat, String[] values) {
this.button = button;
this.remote = remote;
this.repeat = repeat;
this.values = values;
}
/**
* Checks if this Entry matches a given button - remote combination and
* repeat value.
*/
public boolean match(LIRCEvent e) {
return (remote == null || remote.equals(e.getRemote()))
&& (button == null || button.equals(e.getName()))
&& ((repeat == 0) ? (e.getRepeat() == 0) : (e.getRepeat() % repeat == 0));
}
/** Returns the next value of this entry. */
public String nextValue() {
if (values.length == 0) {
return null;
} else {
counter++;
if (counter >= values.length) counter = 0;
return values[counter];
}
}
}
/**
* For testing. Loads a config file, dumps the setup to stdout, connects to
* the LIRC daemon and prints the commands matched by the events received.
*/
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Usage java LIRCMap <config file> [<program>]");
System.exit(1);
}
String prog = (args.length >= 2) ? args[1] : null;
final LIRCMap map = new LIRCMap(prog, new File(args[0]));
map.store(new OutputStreamWriter(System.out));
new LIRCClient().addLIRCListener(new org.lirc.LIRCListener() {
public void received(LIRCEvent e) {
String com = map.get(e);
if (com != null){
System.out.println(e + " => " + com);
}
}
});
try {
while (true) { Thread.sleep(200); }
} catch (InterruptedException ex) {}
}
}