gnome-lirc-properties r2 - in trunk: . bin data data/icons data/icons/16x16 data/icons/22x22 data/icons/24x24 data/icons/32x32 data/icons/48x48 data/icons/scalable debian doc gnome_lirc_properties gnome_lirc_properties/net gnome_lirc_properties/policykit_mechanism gnome_lirc_properties/ui gnome_lirc_properties/upload help help/C help/C/figures man patches po pylint test web



Author: murrayc
Date: Sat Apr 19 09:45:07 2008
New Revision: 2
URL: http://svn.gnome.org/viewvc/gnome-lirc-properties?rev=2&view=rev

Log:
moved from fluendo svn

Added:
   trunk/AUTHORS
   trunk/COPYING
   trunk/ChangeLog
   trunk/INSTALL
   trunk/MAINTAINERS
   trunk/Makefile.am
   trunk/NEWS
   trunk/README
   trunk/TODO
   trunk/autogen.sh   (contents, props changed)
   trunk/bin/
   trunk/bin/.gitignore
   trunk/bin/gnome-lirc-properties.in
   trunk/bin/lirc-receiver-list   (contents, props changed)
   trunk/bin/pylint   (contents, props changed)
   trunk/bin/todo-list   (contents, props changed)
   trunk/configure.ac
   trunk/data/
   trunk/data/.gitignore
   trunk/data/Makefile.am
   trunk/data/gnome-lirc-properties-mechanism.policy.in
   trunk/data/gnome-lirc-properties.desktop.in.in
   trunk/data/gnome-lirc-properties.glade
   trunk/data/icons/
   trunk/data/icons/16x16/
   trunk/data/icons/16x16/Makefile.am
   trunk/data/icons/16x16/gnome-lirc-properties.png   (contents, props changed)
   trunk/data/icons/22x22/
   trunk/data/icons/22x22/Makefile.am
   trunk/data/icons/22x22/gnome-lirc-properties.png   (contents, props changed)
   trunk/data/icons/24x24/
   trunk/data/icons/24x24/Makefile.am
   trunk/data/icons/24x24/gnome-lirc-properties.png   (contents, props changed)
   trunk/data/icons/32x32/
   trunk/data/icons/32x32/Makefile.am
   trunk/data/icons/32x32/gnome-lirc-properties.png   (contents, props changed)
   trunk/data/icons/48x48/
   trunk/data/icons/48x48/Makefile.am
   trunk/data/icons/48x48/gnome-lirc-properties.png   (contents, props changed)
   trunk/data/icons/Makefile.am
   trunk/data/icons/scalable/
   trunk/data/icons/scalable/Makefile.am
   trunk/data/icons/scalable/gnome-lirc-properties.svg
   trunk/data/linux-input-layer-lircd.conf
   trunk/data/org.gnome.LircProperties.Mechanism.conf
   trunk/data/org.gnome.LircProperties.Mechanism.service.in
   trunk/data/overrides.conf
   trunk/data/receivers.conf
   trunk/debian/
   trunk/debian/changelog
   trunk/debian/compat
   trunk/debian/control
   trunk/debian/copyright
   trunk/debian/docs
   trunk/debian/prerm
   trunk/debian/pyversions
   trunk/debian/rules   (contents, props changed)
   trunk/debian/watch
   trunk/doc/
   trunk/doc/lirc.dia   (contents, props changed)
   trunk/doc/lirc.txt
   trunk/gnome_lirc_properties/
   trunk/gnome_lirc_properties/.gitignore
   trunk/gnome_lirc_properties/Makefile.am
   trunk/gnome_lirc_properties/__init__.py
   trunk/gnome_lirc_properties/backend.py
   trunk/gnome_lirc_properties/config.py.in
   trunk/gnome_lirc_properties/hardware.py
   trunk/gnome_lirc_properties/lirc.py
   trunk/gnome_lirc_properties/lsb.py
   trunk/gnome_lirc_properties/model.py
   trunk/gnome_lirc_properties/net/
   trunk/gnome_lirc_properties/net/Makefile.am
   trunk/gnome_lirc_properties/net/MultipartPostHandler.py
   trunk/gnome_lirc_properties/net/__init__.py
   trunk/gnome_lirc_properties/net/services.py
   trunk/gnome_lirc_properties/policykit.py
   trunk/gnome_lirc_properties/policykit_mechanism/
   trunk/gnome_lirc_properties/ui/
   trunk/gnome_lirc_properties/ui/CustomConfiguration.py
   trunk/gnome_lirc_properties/ui/Makefile.am
   trunk/gnome_lirc_properties/ui/ProgressWindow.py
   trunk/gnome_lirc_properties/ui/ReceiverChooserDialog.py
   trunk/gnome_lirc_properties/ui/RemoteControlProperties.py
   trunk/gnome_lirc_properties/ui/__init__.py
   trunk/gnome_lirc_properties/ui/common.py
   trunk/gnome_lirc_properties/upload/
   trunk/help/
   trunk/help/.gitignore
   trunk/help/C/
   trunk/help/C/figures/
   trunk/help/C/figures/auto-detect.png   (contents, props changed)
   trunk/help/C/figures/custom-remote-basics.png   (contents, props changed)
   trunk/help/C/figures/custom-remote-keys.png   (contents, props changed)
   trunk/help/C/figures/custom-remote-model.png   (contents, props changed)
   trunk/help/C/figures/main-window.png   (contents, props changed)
   trunk/help/C/gnome-lirc-properties.xml
   trunk/help/C/legal.xml
   trunk/help/ChangeLog
   trunk/help/Makefile.am
   trunk/help/gnome-lirc-properties.omf.in
   trunk/man/
   trunk/man/gnome-lirc-properties.1
   trunk/patches/
   trunk/patches/0001-Use-new-instead-of-conf-as-filename-suffix.patch
   trunk/patches/0002-Add-resume-switch-to-irrecord.patch
   trunk/po/
   trunk/po/.gitignore
   trunk/po/ChangeLog
   trunk/po/POTFILES.in
   trunk/po/POTFILES.skip
   trunk/pylint/
   trunk/pylint/custom-checker.py
   trunk/pylintrc
   trunk/test/
   trunk/test/Makefile.am
   trunk/test/example.lircrc
   trunk/test/test-backend-service.py
   trunk/test/test-lirc-client.c
   trunk/test/test-lirc-key-listener.py   (contents, props changed)
   trunk/test/test-policykit-is-authorized.py
   trunk/test/test-policykit-obtain-authorization.py   (contents, props changed)
   trunk/test/test-policykit.py   (contents, props changed)
   trunk/test/test-pylirc.py   (contents, props changed)
   trunk/test/test-run-backend-service.sh   (contents, props changed)
   trunk/test/test-udp-receiver.py   (contents, props changed)
   trunk/test/test-uploader.py   (contents, props changed)
   trunk/web/
   trunk/web/README
   trunk/web/service.wsgi

Added: trunk/AUTHORS
==============================================================================
--- (empty file)
+++ trunk/AUTHORS	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,2 @@
+Mathias Hasselmann <mathias openismus com>
+Murray Cumming <murrayc murrayc com>

Added: trunk/COPYING
==============================================================================
--- (empty file)
+++ trunk/COPYING	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.

Added: trunk/INSTALL
==============================================================================
--- (empty file)
+++ trunk/INSTALL	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,234 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006 Free Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package.  The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.
+
+     Running `configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about.  Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug.  Until the bug is fixed you can use this workaround:
+
+     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+     Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+

Added: trunk/MAINTAINERS
==============================================================================
--- (empty file)
+++ trunk/MAINTAINERS	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,7 @@
+Mathias Hasselmann
+E-mail: mathias openismus com
+Userid: hasselmm
+
+Murray Cumming
+E-mail: murrayc openismus com
+Userid: murrayc

Added: trunk/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,28 @@
+SUBDIRS = data gnome_lirc_properties help po
+bin_SCRIPTS = bin/gnome-lirc-properties
+man1_MANS = man/gnome-lirc-properties.1
+
+EXTRA_DIST = \
+	bin/lirc-receiver-list \
+	bin/todo-list \
+	data/overrides.conf \
+	gnome-doc-utils.make \
+	intltool-extract.in \
+	intltool-merge.in \
+	intltool-update.in \
+	man/gnome-lirc-properties.1 \
+	patches/0001-Use-new-instead-of-conf-as-filename-suffix.patch \
+	patches/0002-Add-resume-switch-to-irrecord.patch \
+	web/README \
+	web/service.wsgi
+
+DISTCLEANFILES = \
+	intltool-extract \
+	intltool-merge \
+	intltool-update
+
+DISTCHECK_CONFIGURE_FLAGS = \
+	--disable-scrollkeeper \
+	--with-lirc-confdir=/etc/lirc \
+	--with-remotes-database=/usr/share/lirc/remotes
+

Added: trunk/NEWS
==============================================================================
--- (empty file)
+++ trunk/NEWS	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,89 @@
+0.2.6:
+
+* Check the lirc configuration files at startup and offer to fix them if 
+  necessary.
+  (Mathias Hasselmann)
+* Auto-detection: Write the configuration files even if the auto-detection 
+  result is the same as what was currently selected, in case the 
+  configuration files are incorrect.
+  (Mathias Hasselmann, Murray Cumming)
+* Handle some other small errors while parsing/writing configuration files.
+  (Mathias Hasselmann)
+* Build:
+  - Add -disable-confdir-check to prevent checking for lircd.conf.
+  - Cleaned up the .desktop file.
+  (Bastien Nocera)
+* Documentation:
+  - Updated the screenshots.
+ 
+0.2.5:
+
+* Generic support for Linux Input Layer remotes, including hot-plugging.
+* Update URL of the remotes database web service.
+* Better focus handling when learning key-codes.
+* Better feedback when learning key-codes.
+* Overall improved robustness of the code.
+
+0.2.4:
+
+* Add button for learning keys for better usability.
+* Fix some threading and DBUS issues.
+
+0.2.3:
+
+* There is no 0.2.3 release due some limitations of Launchpad's Personal
+  Package Archive service: You cannot replace release candidate tarballs.
+
+0.2.2:
+
+* Support Hardy Heron's include statements for lircd.conf files.
+* Probably handle the result of PolicyKit's ObtainAuthorization() method.
+* Better error handling when invoking help browser.
+* LIRC related sanity checks in configure script.
+
+0.2.1:
+
+* Finish remote configuration sharing.
+* Add server-side remote database script.
+* Add application icon drawn by Andreas Nilsson.
+* Update .desktop file to show up properly.
+* Fix problems caused by latest refactoring.
+* Improvements do the user manual.
+
+0.2.0:
+
+* Setup initial help infrastructure.
+* Implement download of custom remote configurations.
+* Major code cleanups and refactoring.
+* Bugfixes.
+
+0.1.0:
+
+* Support learning of "MODE2" receivers.
+* Support UDP receiver for testing.
+* Find device nodes for dev/input and USB/HID receivers.
+* Initial tool for extracting receivers.conf from lirc source code.
+* Allow retry of failed receiver detection.
+* Major code cleanups to make pylint happy.
+* Bugfixes.
+
+0.0.4:
+
+* Implement key-code learning.
+* Bugfixes.
+
+0.0.3:
+
+* Mostly bugfixes.
+
+0.0.2:
+
+* The PolicyKit stuff all seems to work now.
+* Added custom configuration dialog.
+* Initial Ubuntu packaging.
+* Drop pylirc dependency.
+* Bugfixes.
+
+0.0.1:
+
+* Mostly useless first release to get feedback about the use of PolicyKit.

Added: trunk/README
==============================================================================
--- (empty file)
+++ trunk/README	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,39 @@
+INFRARED REMOTE CONTROL CONFIGURATION
+=====================================
+
+This is a small application like a GNOME control-panel, to allow configuration
+of lirc for a user's remote control, and verification that the configuration is
+correct.
+
+It is implemented with GTK+, using Python via PyGtk. A working PolicyKit
+authentication agent, as provided for instance by PolicyKit-gnome, is
+required for administrative tasks.
+
+INSTALLATION NOTES
+------------------
+
+You probably want to use these configure options, at least on Ubuntu,
+to ensure that all the PolicyKit and D-Bus stuff is installed in the
+appropriate place:
+
+    ./configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/lib
+
+Get PolicyKit and PolicyKit-gnome from <http://hal.freedesktop.org/releases/>,
+if your package manager doesn't list them already.
+
+
+TROUBLESHOOTING
+---------------
+
+Try running gnome-lirc-properties from the command line to see if there is 
+any useful output, such as a python backtrace.
+
+Try running the gnome-lirc-properties D-Bus service from the command line, so 
+you can see any output from it too:
+
+$ sudo killall gnome_lirc_properties.backend
+$ sudo python -m gnome_lirc_properties.backend
+
+You'll need to start using gnome-lirc-properties quickly because that instance 
+of the D-Bus service will automatically die after a short time if it is not 
+used.

Added: trunk/TODO
==============================================================================
--- (empty file)
+++ trunk/TODO	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,13 @@
+gnome_lirc_properties/lirc.py:530:
+    Add other keys (and alternatives key names):
+
+gnome_lirc_properties/lirc.py:678:
+    These probably shouldn't be here, at least when standard key names
+    have been agreed. Our own lircd.conf files should use only the
+    standard names, and we should probably warn about (or ignore as
+    broken) non-standard key names, because applications are unlikely to
+    work with those broken lircd.conf files.
+
+gnome_lirc_properties/ui/CustomConfiguration.py:463:
+    Stop the key-listener, when we know that lircd is disabled.
+

Added: trunk/autogen.sh
==============================================================================
--- (empty file)
+++ trunk/autogen.sh	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME=gnome-lirc-properties
+REQUIRED_AUTOMAKE_VERSION=1.10
+
+if ! (test -f "$srcdir/configure.ac" &&
+      test -f "$srcdir/bin/gnome-lirc-properties.in" &&
+      test -f "$srcdir/data/gnome-lirc-properties.glade" &&
+      test -f "$srcdir/data/gnome-lirc-properties.desktop.in.in")
+then
+ echo "$srcdir doesn't look like source directory for $PKG_NAME" >&2
+ exit 1
+fi
+
+. gnome-autogen.sh
+

Added: trunk/bin/.gitignore
==============================================================================
--- (empty file)
+++ trunk/bin/.gitignore	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+gnome-lirc-properties

Added: trunk/bin/gnome-lirc-properties.in
==============================================================================
--- (empty file)
+++ trunk/bin/gnome-lirc-properties.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,29 @@
+#! PYTHON@
+
+import gettext, locale, os.path, sys
+
+locale.setlocale(locale.LC_ALL, '')
+
+datadir = os.path.join('@prefix@', 'share')
+bindir  = os.path.dirname(os.path.realpath(__file__))
+srcdir  = os.path.dirname(bindir)
+
+probe   = os.path.join(srcdir, 'gnome_lirc_properties', 'config.py.in')
+
+if os.path.isfile(probe):
+    print 'Running uninstalled version at %s...' % srcdir
+
+    datadir = os.path.join(srcdir, 'data')
+    sys.path.insert(0, srcdir)
+
+else:
+    localedir = os.path.join(datadir, 'locale')
+    gettext.bindtextdomain('@GETTEXT_PACKAGE@', localedir)
+
+    datadir = os.path.join(datadir, '@PACKAGE@')
+
+import gnome_lirc_properties
+
+gnome_lirc_properties.run(sys.argv[1:], datadir)
+
+# vim: ft=python

Added: trunk/bin/lirc-receiver-list
==============================================================================
--- (empty file)
+++ trunk/bin/lirc-receiver-list	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,362 @@
+#!/usr/bin/python
+
+# Extract remote descriptions from annotated source code
+# Copyright (C) 2008 Openismus GmbH (www.openismus.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# Authors:
+#   Mathias Hasselmann <mathias openismus com>
+#
+# Usage:
+#   build-receiver-list [SRCDIR]
+#
+import gzip, logging, os, pwd, re, sys
+
+from ConfigParser import SafeConfigParser
+from datetime     import datetime
+
+class DeviceDatabase(dict):
+    re_record = re.compile(r'^(\t*)([0-9A-Fa-f]{4})\s+(.*)\s*$')
+
+    class Record(dict):
+        def __init__(self, code, name):
+            super(DeviceDatabase.Record, self).__init__()
+
+            self.__code = code
+            self.__name = name
+
+        def __str__(self):
+            return self.name
+
+        def __repr__(self):
+            return '<%s: %r>' % (self.name, dict(self.items()))
+
+        code = property(lambda self: self.__code)
+        name = property(lambda self: self.__name)
+
+    def __init__(self, fileobj):
+        super(DeviceDatabase, self).__init__()
+        vendor, device = None, None
+
+        for line in fileobj:
+            match = self.re_record.match(line)
+
+            if not match:
+                continue
+
+            prefix, code, name = match.groups()
+            code = int(code, 16)
+
+            if 0 == len(prefix):
+                vendor, device = self.Record(code, name), None
+                self[code] = vendor
+                continue
+
+            if 1 == len(prefix):
+                device = self.Record(code, name)
+                vendor[code] = device
+                continue
+
+            if 2 == len(prefix):
+                iface = self.Record(code, name)
+                device[code] = iface
+                continue
+
+def scan_userspace_driver(filename):
+    srcname = filename[len(srcdir):].strip(os.sep)
+    driver_code = open(filename).read()
+
+    for match in re_hardware.finditer(driver_code):
+        line_number = driver_code[:match.start(1)].count('\n')
+        declaration = match.group(1)
+
+        # convert decaration text into list of C expressions:
+        expressions = re_comments.sub('', declaration).split(',')
+        expressions = [value.strip() for value in expressions]
+
+        # last expression is the driver name:
+        driver_name = expressions[-1].strip('"')
+
+        if not driver_name:
+            continue
+
+        #print '# TODO: %s from %s, line %d' % (driver_name, srcname, line_number)
+        # TODO: extract information for user-space drivers
+
+def expand_symbols(symbols, text):
+    def replace_symbol(match):
+        # lookup word in symbol table:
+        expansion = symbols.get(match.group(0))
+
+        if expansion:
+            # expand symbol recursively when found:
+            return expand_symbols(symbols, expansion)
+
+        return match.group(0)
+
+    return re.sub(r'\b\w+\b', replace_symbol, text)
+
+def override_name(overrides, name, suffix):
+    key = '%s-%s' % (name.lower(), suffix)
+    return overrides.get(key, name)
+
+def derive_name(lookup, match, **overrides):
+    derived = match and match.group(1).title() or None
+
+    if derived:
+        if len(derived) < 4:
+            derived = derived.upper()
+
+        for suffix, values in overrides.items():
+            derived = override_name(values, derived, suffix)
+
+    if derived and lookup is not None:
+        if lookup.name.lower().find(derived.lower()) >= 0:
+            return lookup.name
+
+        return '%s/%s' % (lookup.name, derived)
+
+    if lookup is not None:
+        return lookup.name
+    if derived:
+        return derived
+
+    return None
+
+def scan_kernel_driver(filename):
+    srcname = filename[len(srcdir):].strip(os.sep)
+    driver_code = open(filename).read()
+
+    def identify_usb_vendor(vendor_id):
+        vendor_match = (
+            vendor_id not in bad_vendor_tokens and
+            re_usb_vendor.match(vendor_id) or
+            None)
+
+        vendor_id = int(expand_symbols(symbols, vendor_id), 0)
+
+        vendor_lookup = usb_ids.get(vendor_id)
+        vendor_name = derive_name(vendor_lookup, vendor_match, vendor=usb_overrides)
+
+        return vendor_id, vendor_name
+
+    def identify_usb_product(vendor_id, product_id):
+        product_match = re_usb_product.match(product_id)
+        product_id = int(expand_symbols(symbols, product_id), 0)
+
+        product_table = usb_ids.get(vendor_id)
+        product_lookup = product_table.get(product_id) if product_table else None
+        product_name = derive_name(product_lookup, product_match, product=usb_overrides)
+
+        if product_name is None and device_block:
+            product_name = override_name(usb_overrides, device_block, 'product')
+
+        return product_id, product_name
+
+    # naively parse preprocessor symbols:
+    symbols = dict()
+
+    for declaration in re_define.finditer(driver_code):
+        name, value = declaration.groups()
+        symbols[name] = value
+
+    # resolve driver name, from symbol table or filename:
+    driver_name = symbols.get('DRIVER_NAME')
+
+    if not driver_name:
+        dirname     = os.path.dirname(filename)
+        driver_name = os.path.basename(dirname)
+
+    else:
+        driver_name = driver_name.strip('"')
+
+    # iterate source code lines:
+    device_block = None
+
+    for line, text in enumerate(driver_code.splitlines()):
+        match = re_usb_device_block_begin.search(text)
+
+        if match:
+            device_block = match.group(1)
+            continue
+
+        match = re_usb_device_block_end.search(text)
+
+        if match:
+            device_block = None
+            continue
+
+        match = re_usb_device.search(text)
+
+        if match:
+            vendor_id, product_id = match.groups()
+
+            vendor_id, vendor_name = identify_usb_vendor(vendor_id)
+            product_id, product_name = identify_usb_product(vendor_id, product_id)
+
+            # skip hardware that doesn't have unique USB ids:
+            if 0xffff == vendor_id or 0xffff == product_id:
+                continue
+
+            # override vendor and product ids:
+            vendor_name = usb_overrides.get(
+                '%04x-%04x-vendor' % (vendor_id, product_id),
+                vendor_name)
+            product_name = usb_overrides.get(
+                '%04x-%04x-product' % (vendor_id, product_id),
+                product_name)
+            remotes = usb_overrides.get(
+                '%04x-%04x-remotes' % (vendor_id, product_id),
+                None)
+
+            # blame unknown vendor and product ids:
+            if not vendor_name:
+                logging.warning('%s:%d: Unknown Vendor (usb:%04x:%04x)',
+                                srcname, line + 1, vendor_id, product_id)
+                vendor_name = 'Unknown Vendor (usb-%04X)' % vendor_id
+
+            if not product_name:
+                logging.warning('%s:%d: Unknown Product usb:%04x:%04x',
+                                srcname, line + 1, vendor_id, product_id)
+                product_name = 'Unknown Product (usb-%04X-%04X)' % (vendor_id, product_id)
+
+            # drop company type suffixes:
+            vendor_name = re_company_suffix.sub('', vendor_name)
+
+            # print findings:
+            section = '%s: %s' % (vendor_name, product_name)
+            receiver_sections.append(section)
+
+            print '# from %s, line %d' % (srcname, line + 1)
+            print '[%s]' % section
+
+            if remotes is not None:
+                print 'compatible-remotes = %s' % remotes
+
+            print 'kernel-module = %s' % driver_name
+            print 'product-id = 0x%04x' % product_id
+            print 'vendor-id = 0x%04x' % vendor_id
+            print
+
+def print_database_header():
+    realname = pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+
+    print '# LIRC Receiver Database'
+    print '# %s' % ('=' * 70)
+    print '# Generated on %s' % datetime.now().strftime('%c')
+    print '# from %s' % os.path.realpath(srcdir)
+    print '# by %s' % realname
+    print '# %s' % ('=' * 70)
+    print
+
+def print_remaining_sections():
+    remaining_sections = [
+        s for s in overrides.sections() 
+        if ':' in s and s not in receiver_sections]
+
+    for section in remaining_sections:
+        print '# from overrides.conf'
+        print '[%s]' % section
+
+        for key, value in overrides.items(section):
+            print '%s = %s' % (key, value)
+
+        print
+
+def scan_sources(path, scanner):
+    '''Scans a source code director using the supplied scanner.'''
+
+    daemons_path = os.path.join(srcdir, 'daemons')
+
+    for path, subdirs, files in os.walk(path):
+        subdirs[:] = [name for name in subdirs if not name.startswith('.')]
+
+        for name in files:
+            if not name.endswith('.c'): continue
+            if name.startswith('.'): continue
+
+            scanner(os.path.join(path, name))
+
+def find_srcdir():
+    '''Finds the source folder for LIRC.'''
+
+    srcdir = len(sys.argv) > 1 and sys.argv[1] or ''
+    filename = os.path.join(srcdir, 'daemons', 'lircd.c')
+
+    if not os.path.isfile(filename):
+        raise SystemExit, 'No LIRC code found at %s.' % (srcdir and srcdir or os.getcwd())
+
+    return srcdir
+
+if '__main__' == __name__:
+    # initialize logging facilities:
+    logging.BASIC_FORMAT = '%(levelname)s: %(message)s'
+
+    # find lirc sources:
+    srcdir = find_srcdir()
+
+    # declare some frequenty used regular expressions:
+    re_hardware = r'struct\s+hardware\s+hw_\w+\s*=\s*{(.*?)};'
+    re_hardware = re.compile(re_hardware, re.DOTALL)
+
+    re_comments = r'/\*\s*(.*?)\s*\*/'
+    re_comments = re.compile(re_comments, re.DOTALL)
+
+    re_properties = r'^(?:\s|\*)*(\S+)\s*:\s*(.*?)(?:\s|\*)*$'
+    re_properties = re.compile(re_properties)
+
+    re_define = r'^#\s*define\s+(\w+)\s+(.*?)\s*$'
+    re_define = re.compile(re_define, re.MULTILINE)
+
+    re_usb_device_block_begin = r'/\*\s*USB Device ID for (.*) USB Control Board\s\*/'
+    re_usb_device_block_begin = re.compile(re_usb_device_block_begin)
+
+    re_usb_device_block_end = r'{\s*}'
+    re_usb_device_block_end = re.compile(re_usb_device_block_end)
+
+    re_usb_device = r'USB_DEVICE\s*\(\s*([^,]*),\s*(.*?)\s*\)'
+    re_usb_device = re.compile(re_usb_device)
+
+    re_usb_vendor = r'^(?:USB_|VENDOR_)?([A-Z]+?)[0-9]*(?:_VENDOR_ID)?$'
+    re_usb_vendor = re.compile(re_usb_vendor)
+
+    re_usb_product = r'^(?:USB_|PRODUCT_)?([A-Z]+?)[0-9]*(?:_PRODUCT_ID)?$'
+    re_usb_product = re.compile(re_usb_product)
+
+    re_company_suffix = r',?\s+(?:Inc|Corp|Ltd|AG)\.?$'
+    re_company_suffix = re.compile(re_company_suffix)
+
+    # read device id databases:
+    usb_ids = DeviceDatabase(gzip.open('/usr/share/misc/usb.ids'))
+    pci_ids = DeviceDatabase(open('/usr/share/misc/pci.ids'))
+
+    # read overrides databases:
+    overrides = SafeConfigParser()
+    overrides.read('data/overrides.conf')
+    usb_overrides = dict(overrides.items('USB-Overrides'))
+
+    bad_vendor_tokens = filter(None, usb_overrides.get('bad-vendor-tokens', '').split())
+    receiver_sections = list()
+
+    # scan source code for receiver information,
+    # and dump this information immediatly:
+    print_database_header()
+
+    scan_sources(os.path.join(srcdir, 'drivers'), scan_kernel_driver)
+    scan_sources(os.path.join(srcdir, 'daemons'), scan_userspace_driver)
+
+    print_remaining_sections()
+

Added: trunk/bin/pylint
==============================================================================
--- (empty file)
+++ trunk/bin/pylint	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+import os.path, pylint.lint, sys
+
+
+bindir  = os.path.dirname(os.path.realpath(__file__))
+plugins = os.path.join(os.path.dirname(bindir), 'pylint')
+cmdline = sys.argv[1:] or ['gnome_lirc_properties']
+
+sys.path.insert(0, plugins)
+pylint.lint.Run(cmdline)

Added: trunk/bin/todo-list
==============================================================================
--- (empty file)
+++ trunk/bin/todo-list	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+
+import os, re, sys, textwrap
+
+class Task(object):
+    def __init__(self, file, line, text=''):
+        if file.startswith('./'):
+            file = file[2:]
+
+        self.__file = file
+        self.__line = line
+        self.__text = text
+
+    def append(self, text):
+        self.__text += ' %s' % text
+
+    def __str__(self):
+        location = self.__file, self.__line
+
+        text = textwrap.wrap(self.__text)
+        text.insert(0, '%s:%d:' % location)
+
+        return '\n    '.join(text)
+
+def find_task_list(path):
+    re_todo = re.compile(r'^\s*#\s*TODO:?\s*(.*)\s$')
+    re_comment = re.compile(r'^\s*#\s*(.*)\s$')
+
+    task_list, task = list(), None
+
+    for path, dirs, files in os.walk(path):
+        for name in filter(lambda f: f.endswith('.py'), files):
+            filename = os.path.join(path, name)
+            task = None
+
+            for line, text in enumerate(open(filename)):
+                match = re_todo.match(text)
+
+                if match:
+                    text = match.group(1)
+                    task = Task(filename, line + 1, text)
+
+                    task_list.append(task)
+
+                    continue
+
+                match = task and re_comment.match(text)
+
+                if match:
+                    task.append(match.group(1))
+                    continue
+
+                task = None
+
+    return task_list
+
+if '__main__' == __name__:
+    for folder in sys.argv[1:] or ['.']:
+        for task in find_task_list(folder):
+            print '%s\n' % task

Added: trunk/configure.ac
==============================================================================
--- (empty file)
+++ trunk/configure.ac	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,156 @@
+AC_INIT([gnome-lirc-properties], [0.2.6],
+        [https://code.fluendo.com/remotecontrol/trac/])
+
+# tar-ustar asks it to use a sensible tar format that can handle long filenames
+AM_INIT_AUTOMAKE([1.9 tar-ustar])
+
+dnl check for programs ===
+
+AM_PATH_PYTHON(2.4)
+IT_PROG_INTLTOOL([0.35.0])
+
+dnl localization support ===
+
+GETTEXT_PACKAGE="$PACKAGE"
+AC_SUBST([GETTEXT_PACKAGE])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],
+                   ["$GETTEXT_PACKAGE"],
+                   [The gettext package])
+AM_GLIB_GNU_GETTEXT
+
+dnl initialize GNOME help ===
+
+GNOME_DOC_INIT()
+
+dnl check for PolicyKit ===
+
+AC_ARG_ENABLE([policy-kit],
+              [  --disable-policy-kit     don't use PolicyKit for gaining privileges])
+
+if test "$enable_policy_kit" != no
+then
+  PKG_CHECK_MODULES([POLICY_KIT], [polkit >= 0.7
+                                   polkit-gnome >= 0.7])
+  ENABLE_POLICY_KIT=True
+  enable_policy_kit=yes
+else
+  ENABLE_POLICY_KIT=False
+fi
+
+AC_SUBST([ENABLE_POLICY_KIT])
+
+POLICY_KIT_ACTION="org.gnome.lirc-properties.mechanism.configure"
+AC_SUBST([POLICY_KIT_ACTION])
+
+dnl support custom LIRC folders ===
+
+expand_vars() {
+  value=`test "$prefix" == NONE && prefix="$ac_default_prefix"; eval "echo \"$1\""`
+  test "$value" != "$1" && expand_vars "$value" || echo "$value"
+}
+
+AC_ARG_WITH([lirc_confdir],
+            AS_HELP_STRING([--with-lirc-confdir],
+                           [Configuration folder of LIRC, e.g. $sysconfdir/lirc]),
+            [], [with_lirc_confdir=`expand_vars "$sysconfdir/lirc"`])
+
+AC_ARG_WITH([remotes_database],
+            AS_HELP_STRING([--with-remotes-database],
+                           [Path of the system's LIRC remote database, e.g. $datadir/lirc/remotes]),
+            [], [with_remotes_database=`expand_vars "$datadir/lirc/remotes"`])
+
+AC_SUBST([with_lirc_confdir])
+AC_SUBST([with_remotes_database])
+
+AC_MSG_CHECKING([configured LIRC configuration folder])
+
+AC_ARG_ENABLE([confdir-check],
+              AS_HELP_STRING([--disable-confdir-check],
+                             [don't check for the lircd.conf path])])
+
+if test "x$enable_confdir_check" = xyes
+  then if test -f "$with_lirc_confdir/lircd.conf"
+    then
+      AC_MSG_RESULT([$with_lirc_confdir])
+    else
+      AC_MSG_RESULT([no])
+      AC_MSG_ERROR([Cannot find lircd.conf in $with_lirc_confdir.])
+    fi
+  else
+    AC_MSG_RESULT([$with_lirc_confdir])
+fi
+
+AC_MSG_CHECKING([configured LIRC remotes database])
+
+if test -d "$with_remotes_database"
+then
+  AC_MSG_RESULT([$with_remotes_database])
+else
+  AC_MSG_RESULT([no])
+  AC_MSG_ERROR([Configured remotes database does not exist: $with_remotes_database])
+fi
+
+dnl support custom upload and download locations ===
+
+AC_ARG_WITH([download_uri],
+            AS_HELP_STRING([--with-download-uri],
+                           [The URI for downloading user-submitted lircd.conf files.]),
+            [], [with_download_uri="http://lirc.fluendo.com/lircdb/remotes.tar.gz";])
+AC_ARG_WITH([upload_uri],
+            AS_HELP_STRING([--with-upload-uri],
+                           [The URI to use for user-submitted lircd.conf files.]),
+            [], [with_upload_uri="http://lirc.fluendo.com/lircdb/upload/";])
+
+AC_SUBST([with_download_uri])
+AC_SUBST([with_upload_uri])
+
+dnl find irrecord ===
+
+AC_PATH_PROG([LIRC_IRRECORD], [irrecord])
+AC_ARG_VAR([LIRC_IRRECORD], [path of the irrecord program])
+AC_SUBST([LIRC_IRRECORD])
+
+AC_MSG_CHECKING([if irrecord supports repeative key lerning])
+if "$LIRC_IRRECORD" --help | grep -wq  -- --resume
+then
+  AC_MSG_RESULT([yes])
+else
+  AC_MSG_RESULT([no])
+
+  patch=`ls "${srcdir}/patches/"*resume*irrecord*`
+  AC_MSG_WARN([$LIRC_IRRECORD doesn't support the --resume switch. Please grab its sources and apply $patch.])
+fi
+
+dnl generate files ===
+
+AC_CONFIG_FILES([Makefile
+                 data/Makefile
+                 data/gnome-lirc-properties.desktop.in
+                 data/gnome-lirc-properties-mechanism.policy
+                 data/org.gnome.LircProperties.Mechanism.service
+                 data/icons/Makefile
+                 data/icons/16x16/Makefile
+                 data/icons/22x22/Makefile
+                 data/icons/24x24/Makefile
+                 data/icons/scalable/Makefile
+                 gnome_lirc_properties/Makefile
+                 gnome_lirc_properties/config.py
+                 gnome_lirc_properties/net/Makefile
+                 gnome_lirc_properties/ui/Makefile
+                 help/Makefile
+                 po/Makefile.in])
+AC_CONFIG_FILES([bin/gnome-lirc-properties],
+                [chmod +x bin/gnome-lirc-properties])
+
+AC_OUTPUT()
+
+dnl print configuration status ===
+
+AC_MSG_NOTICE([=====================================================================])
+AC_MSG_NOTICE([  PolicyKit support: $enable_policy_kit])
+AC_MSG_NOTICE([  Remotes database: $with_remotes_database])
+AC_MSG_NOTICE([  IR record tool: $LIRC_IRRECORD])
+AC_MSG_NOTICE([  Download URI: $with_download_uri])
+AC_MSG_NOTICE([  Upload URI: $with_upload_uri])
+AC_MSG_NOTICE([=====================================================================])
+

Added: trunk/data/.gitignore
==============================================================================
--- (empty file)
+++ trunk/data/.gitignore	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,4 @@
+gnome-lirc-properties-mechanism.policy
+gnome-lirc-properties.desktop
+gnome-lirc-properties.desktop.in
+org.gnome.LircProperties.Mechanism.service

Added: trunk/data/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,43 @@
+SUBDIRS = icons
+
+# Most distros will probably want to use --sysconfdir=/etc,
+# because that is what most distros have configured D-Bus to use.
+dbusconfdir = $(sysconfdir)/dbus-1/system.d
+dbusconf_DATA = org.gnome.LircProperties.Mechanism.conf
+
+policykitpolicydir = $(datadir)/PolicyKit/policy
+policykitpolicy_in_files = gnome-lirc-properties-mechanism.policy.in
+policykitpolicy_DATA = gnome-lirc-properties-mechanism.policy
+
+# The .service file is generated from a .service.in file
+# so that the install location can be inserted.
+servicedir = $(datadir)/dbus-1/system-services
+service_in_files = org.gnome.LircProperties.Mechanism.service.in
+service_DATA = org.gnome.LircProperties.Mechanism.service
+
+# The .desktop file is generated from a .desktop.in file
+# so that intltool can take care of localization.
+desktopdir = $(datadir)/applications
+desktop_in_files = gnome-lirc-properties.desktop.in
+desktop_DATA = gnome-lirc-properties.desktop
+
+resourcesdir = $(pkgdatadir)
+
+resources_DATA = \
+	gnome-lirc-properties.glade \
+	linux-input-layer-lircd.conf \
+	receivers.conf
+
+EXTRA_DIST = \
+	$(dbusconf_DATA) \
+	$(desktop_in_files) \
+	$(policykitpolicy_in_files) \
+	$(resources_DATA) \
+	$(service_in_files)
+
+DISTCLEANFILES = \
+	$(desktop_DATA) \
+	$(policykitpolicy_DATA) \
+	$(service_DATA)
+
+ INTLTOOL_DESKTOP_RULE@

Added: trunk/data/gnome-lirc-properties-mechanism.policy.in
==============================================================================
--- (empty file)
+++ trunk/data/gnome-lirc-properties-mechanism.policy.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd";>
+
+<!-- needs i18n work -->
+
+<policyconfig>
+  <vendor>Fluendo Embedded S.L.</vendor>
+  <vendor_url>http://www.fluendo.com/</vendor_url>
+  <icon_name>gnome-lirc-properties</icon_name>
+
+  <action id="@POLICY_KIT_ACTION@">
+    <description>Change LIRC Configuration</description>
+    <message>Changing the LIRC configuration requires privileges.</message>
+    <defaults>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>auth_self_keep_always</allow_active>
+    </defaults>
+  </action>
+
+</policyconfig>

Added: trunk/data/gnome-lirc-properties.desktop.in.in
==============================================================================
--- (empty file)
+++ trunk/data/gnome-lirc-properties.desktop.in.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,10 @@
+[Desktop Entry]
+_Name=Infrared Remote Control
+_GenericName=Infrared Remote Control
+_Comment=Configure your remote control
+Terminal=false
+Type=Application
+Exec= prefix@/bin/gnome-lirc-properties
+Icon=gnome-lirc-properties
+StartupNotify=true
+Categories=GNOME;GTK;Settings;HardwareSettings;System;

Added: trunk/data/gnome-lirc-properties.glade
==============================================================================
--- (empty file)
+++ trunk/data/gnome-lirc-properties.glade	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,1277 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="lirc_properties_dialog">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Remote Control Properties</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <signal name="realize" handler="_on_lirc_properties_dialog_realize"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox3">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="border_width">12</property>
+            <property name="spacing">18</property>
+            <child>
+              <widget class="GtkFrame" id="frame_receivers">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment_receivers">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox_receivers">
+                        <property name="visible">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkTable" id="table_receiver_selection">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="n_rows">4</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">6</property>
+                            <property name="row_spacing">6</property>
+                            <child>
+                              <widget class="GtkLabel" id="label_device">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">_Device:</property>
+                                <property name="use_underline">True</property>
+                              </widget>
+                              <packing>
+                                <property name="top_attach">2</property>
+                                <property name="bottom_attach">3</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="vendor-label">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">_Manufacturer:</property>
+                                <property name="use_underline">True</property>
+                                <property name="mnemonic_widget">combo_receiver_vendor_list</property>
+                              </widget>
+                              <packing>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="product-label">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">M_odel:</property>
+                                <property name="use_underline">True</property>
+                                <property name="mnemonic_widget">combo_receiver_product_list</property>
+                              </widget>
+                              <packing>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkComboBox" id="combo_receiver_vendor_list">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="items" translatable="yes"></property>
+                                <signal name="changed" handler="_on_vendor_list_changed"/>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkComboBox" id="combo_receiver_product_list">
+                                <property name="width_request">400</property>
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="focus_on_click">False</property>
+                                <property name="items" translatable="yes"></property>
+                                <signal name="changed" handler="_on_product_list_changed"/>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="label_device_name">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="xalign">0</property>
+                                <property name="xpad">4</property>
+                                <property name="label" translatable="yes">&lt;small&gt;&lt;/small&gt;</property>
+                                <property name="use_markup">True</property>
+                                <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">3</property>
+                                <property name="bottom_attach">4</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="label_invisible">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                              </widget>
+                              <packing>
+                                <property name="top_attach">3</property>
+                                <property name="bottom_attach">4</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkHBox" id="hbox_device">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <child>
+                                  <widget class="GtkComboBoxEntry" id="combo_device">
+                                    <property name="visible">True</property>
+                                    <property name="sensitive">False</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <signal name="changed" handler="_on_combo_device_changed"/>
+                                    <child internal-child="entry">
+                                      <widget class="GtkEntry" id="comboboxentry-entry2">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                      </widget>
+                                    </child>
+                                  </widget>
+                                </child>
+                                <child>
+                                  <widget class="GtkAlignment" id="alignment_spinbutton_device">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <child>
+                                      <widget class="GtkSpinButton" id="spinbutton_device">
+                                        <property name="can_focus">True</property>
+                                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                        <property name="adjustment">0 0 100 1 10 10</property>
+                                        <signal name="value_changed" handler="_on_spinbutton_device_value_changed"/>
+                                      </widget>
+                                    </child>
+                                  </widget>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">2</property>
+                                <property name="bottom_attach">3</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkAlignment" id="alignment_auto_detect">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="yscale">0</property>
+                            <child>
+                              <widget class="GtkButton" id="button_auto_detect">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="label" translatable="yes">_Auto-detect</property>
+                                <property name="use_underline">True</property>
+                                <property name="response_id">0</property>
+                                <signal name="clicked" handler="_on_button_auto_detect_clicked"/>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkHBox" id="hbox_auto_detect_progress">
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <widget class="GtkAlignment" id="alignment_auto_detect_progress">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="yscale">0</property>
+                                <child>
+                                  <widget class="GtkProgressBar" id="progressbar_auto_detect">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="show_text">True</property>
+                                    <property name="fraction">0.25</property>
+                                    <property name="text" translatable="yes">Searching for IR receivers</property>
+                                    <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+                                  </widget>
+                                </child>
+                              </widget>
+                            </child>
+                            <child>
+                              <widget class="GtkButton" id="auto-detect-stop-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="label" translatable="yes">gtk-cancel</property>
+                                <property name="use_stock">True</property>
+                                <property name="response_id">0</property>
+                                <signal name="clicked" handler="_on_auto_detect_stop_button_clicked"/>
+                              </widget>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_receivers">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="label" translatable="yes">&lt;b&gt;IR Receiver&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame_remote">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment_remote">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox_remote">
+                        <property name="visible">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkRadioButton" id="radiobutton_supplied_remote">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="label" translatable="yes">Use _supplied remote control</property>
+                            <property name="use_underline">True</property>
+                            <property name="response_id">0</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                            <accessibility>
+                              <atkproperty name="AtkObject::accessible_description" translatable="yes">Use the remote control that was supplied with the infra-red receiver, if any.</atkproperty>
+                            </accessibility>
+                            <signal name="toggled" handler="_on_radiobutton_supplied_remote_toggled"/>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkRadioButton" id="radiobutton_other_remote">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="label" translatable="yes">Use d_ifferent remote control</property>
+                            <property name="use_underline">True</property>
+                            <property name="response_id">0</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                            <property name="group">radiobutton_supplied_remote</property>
+                            <accessibility>
+                              <atkproperty name="AtkObject::accessible_description" translatable="yes">Use a remote control that was not supplied with the infra-red receiver, such as a generic replacement remote control not specifically designed for use with a computer.</atkproperty>
+                            </accessibility>
+                            <signal name="size_allocate" handler="_on_radiobutton_other_remote_size_allocate"/>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkAlignment" id="alignment_remote_selection">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <widget class="GtkTable" id="table_remote_selection">
+                                <property name="visible">True</property>
+                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                <property name="n_rows">3</property>
+                                <property name="n_columns">2</property>
+                                <property name="column_spacing">6</property>
+                                <property name="row_spacing">6</property>
+                                <child>
+                                  <widget class="GtkHBox" id="hbox_remote_actions">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="spacing">6</property>
+                                    <child>
+                                      <widget class="GtkButton" id="button_download">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                        <property name="tooltip" translatable="yes">Download custom configurations</property>
+                                        <property name="label" translatable="yes">_Update</property>
+                                        <property name="use_underline">True</property>
+                                        <property name="response_id">0</property>
+                                        <signal name="clicked" handler="_on_button_download_clicked"/>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="pack_type">GTK_PACK_END</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <widget class="GtkButton" id="button_custom">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                        <property name="label" translatable="yes">Cus_tom Configuration</property>
+                                        <property name="use_underline">True</property>
+                                        <property name="response_id">0</property>
+                                        <signal name="clicked" handler="_on_custom_configuration_button_clicked"/>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="pack_type">GTK_PACK_END</property>
+                                      </packing>
+                                    </child>
+                                  </widget>
+                                  <packing>
+                                    <property name="right_attach">2</property>
+                                    <property name="top_attach">2</property>
+                                    <property name="bottom_attach">3</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkLabel" id="label_remote_vendor">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">Ma_nufacturer:</property>
+                                    <property name="use_underline">True</property>
+                                    <property name="mnemonic_widget">combo_remote_vendor_list</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="x_options">GTK_FILL</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkLabel" id="label_remote_product">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">Mod_el:</property>
+                                    <property name="use_underline">True</property>
+                                    <property name="mnemonic_widget">combo_remote_product_list</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="top_attach">1</property>
+                                    <property name="bottom_attach">2</property>
+                                    <property name="x_options">GTK_FILL</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkComboBox" id="combo_remote_vendor_list">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="items" translatable="yes"></property>
+                                    <signal name="changed" handler="_on_remote_vendor_list_changed"/>
+                                  </widget>
+                                  <packing>
+                                    <property name="left_attach">1</property>
+                                    <property name="right_attach">2</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkComboBox" id="combo_remote_product_list">
+                                    <property name="visible">True</property>
+                                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                                    <property name="focus_on_click">False</property>
+                                    <property name="items" translatable="yes"></property>
+                                    <signal name="changed" handler="_on_remote_product_list_changed"/>
+                                  </widget>
+                                  <packing>
+                                    <property name="left_attach">1</property>
+                                    <property name="right_attach">2</property>
+                                    <property name="top_attach">1</property>
+                                    <property name="bottom_attach">2</property>
+                                  </packing>
+                                </child>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_remote">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="label" translatable="yes">&lt;b&gt;IR Remote Control&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame_preview">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment_preview">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox_preview">
+                        <property name="visible">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkLabel" id="label_preview_status">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Press remote control buttons to test:</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label_preview_result">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">&lt;none&gt;</property>
+                            <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_preview">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Configuration Test&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area3">
+            <property name="visible">True</property>
+            <property name="homogeneous">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="helpbutton3">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="label">gtk-help</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-11</property>
+                <signal name="clicked" handler="_on_button_help_clicked"/>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="unlockbutton">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="has_focus">True</property>
+                <property name="label">_Unlock</property>
+                <property name="use_underline">True</property>
+                <property name="response_id">2</property>
+                <signal name="clicked" handler="_on_button_unlock_clicked"/>
+              </widget>
+              <packing>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="closebutton3">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="label">gtk-close</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">2</property>
+                <signal name="clicked" handler="_on_button_close_clicked"/>
+              </widget>
+              <packing>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="receiver_chooser_dialog">
+    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+    <property name="border_width">5</property>
+    <property name="modal">True</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="transient_for">lirc_properties_dialog</property>
+    <property name="has_separator">False</property>
+    <signal name="delete_event" handler="_on_delete_event"/>
+    <signal name="response" handler="_on_receiver_chooser_dialog_response"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox2">
+        <property name="visible">True</property>
+        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox5">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="border_width">6</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkLabel" id="label8">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Multiple Receivers Detected.&lt;/b&gt;&lt;/big&gt;
+Please choose the IR receiver that you wish to use.</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkScrolledWindow" id="scrolledwindow2">
+                <property name="width_request">550</property>
+                <property name="height_request">250</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="shadow_type">GTK_SHADOW_IN</property>
+                <child>
+                  <widget class="GtkTreeView" id="receiver_view">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="headers_visible">False</property>
+                    <signal name="row_activated" handler="_on_receiver_view_row_activated"/>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area2">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="receiver_chooser_cancel">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="receiver_chooser_accept">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-apply</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-3</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="custom_configuration">
+    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+    <property name="border_width">5</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="transient_for">lirc_properties_dialog</property>
+    <property name="has_separator">False</property>
+    <signal name="close" handler="_on_close"/>
+    <signal name="response" handler="_on_response"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox4">
+        <property name="visible">True</property>
+        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkNotebook" id="notebook">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <child>
+              <widget class="GtkTable" id="page_remote_model">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="border_width">12</property>
+                <property name="n_rows">4</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">6</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <widget class="GtkAlignment" id="usage_hint">
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="yalign">0</property>
+                    <property name="yscale">0</property>
+                    <property name="top_padding">6</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox_usage_hint">
+                        <property name="visible">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkImage" id="image_usage_hint">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="stock">gtk-info</property>
+                            <property name="icon_size">1</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label_usage_hint1">
+                            <property name="visible">True</property>
+                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Please enter the manufacturer and model name.</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="padding-or-maybe-instructions">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="label" translatable="yes">
+
+
+
+
+
+
+</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="entry_contributor">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="activates_default">True</property>
+                    <signal name="changed" handler="_on_dialog_changed"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_contributor">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Co_ntributor:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">entry_contributor</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_product">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Mo_del:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">entry_product</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label_vendor">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">_Manufacturer:</property>
+                    <property name="use_markup">True</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">entry_vendor</property>
+                  </widget>
+                  <packing>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="entry_vendor">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="activates_default">True</property>
+                    <signal name="changed" handler="_on_dialog_changed"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="entry_product">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="activates_default">True</property>
+                    <signal name="changed" handler="_on_dialog_changed"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label_remote_model">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">_Remote Model</property>
+                <property name="use_underline">True</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="page_basics">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="border_width">12</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label_basics_hint">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Key codes cannot be received
+until these basic parameters are identified.</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow_basics">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="GtkTreeView" id="treeview_basics">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHBox" id="hbox_detect_basics">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <widget class="GtkProgressBar" id="progressbar_detect_basics">
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button_detect_basics">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="label" translatable="yes">_Detect</property>
+                        <property name="use_underline">True</property>
+                        <property name="response_id">0</property>
+                        <signal name="clicked" handler="_on_detect_button_clicked"/>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="pack_type">GTK_PACK_END</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label_basics">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">_Basic Configuration</property>
+                <property name="use_underline">True</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="position">1</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="page_keys">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="border_width">12</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkHBox" id="hbox_keys_hint">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <widget class="GtkLabel" id="label_keys_hint">
+                        <property name="visible">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Choose a button to redefine and press "Learn", or add another button.
+Try to use key names from the default namespace only for best interoperability.</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkImage" id="image_keys_hint">
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="stock">gtk-dialog-info</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="pack_type">GTK_PACK_END</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow_keys">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="GtkTreeView" id="treeview_keys">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <signal name="row_activated" handler="_on_treeview_keys_row_activated"/>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHButtonBox" id="hbuttonbox_keys">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="spacing">6</property>
+                    <property name="layout_style">GTK_BUTTONBOX_START</property>
+                    <child>
+                      <widget class="GtkButton" id="button_keys_add">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="label" translatable="yes">gtk-add</property>
+                        <property name="use_stock">True</property>
+                        <property name="focus_on_click">False</property>
+                        <property name="response_id">0</property>
+                        <signal name="clicked" handler="_on_button_keys_add_clicked"/>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button_keys_remove">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="label" translatable="yes">gtk-remove</property>
+                        <property name="use_stock">True</property>
+                        <property name="focus_on_click">False</property>
+                        <property name="response_id">0</property>
+                        <signal name="clicked" handler="_on_button_keys_remove_clicked"/>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button_keys_clear">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="label" translatable="yes">gtk-clear</property>
+                        <property name="use_stock">True</property>
+                        <property name="focus_on_click">False</property>
+                        <property name="response_id">0</property>
+                        <signal name="clicked" handler="_on_button_keys_clear_clicked"/>
+                      </widget>
+                      <packing>
+                        <property name="position">2</property>
+                        <property name="secondary">True</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkToggleButton" id="button_keys_learn">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="label" translatable="yes">_Learn</property>
+                        <property name="use_underline">True</property>
+                        <property name="response_id">0</property>
+                        <signal name="toggled" handler="_on_button_keys_learn_toggled"/>
+                      </widget>
+                      <packing>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label_keys">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">_Key Codes</property>
+                <property name="use_underline">True</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="position">2</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area4">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="button_ok">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="button_upload">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="tooltip" translatable="yes">Upload to Online Database</property>
+                <property name="label" translatable="yes">_Upload</property>
+                <property name="use_underline">True</property>
+                <property name="response_id">1</property>
+                <signal name="clicked" handler="_on_button_upload_clicked"/>
+              </widget>
+              <packing>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">1</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="button_cancel">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkWindow" id="progress_window">
+    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+    <property name="type">GTK_WINDOW_POPUP</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="skip_pager_hint">True</property>
+    <child>
+      <widget class="GtkFrame" id="frame_progress">
+        <property name="visible">True</property>
+        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+        <property name="label_xalign">0</property>
+        <property name="shadow_type">GTK_SHADOW_OUT</property>
+        <child>
+          <widget class="GtkVBox" id="vbox_progress">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="border_width">12</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkLabel" id="label_progress_title">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Some Action&lt;/b&gt;&lt;/big&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkProgressBar" id="progressbar">
+                <property name="width_request">300</property>
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="text" translatable="yes"></property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label_progress_detail">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">Some Detail...</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </widget>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>

Added: trunk/data/icons/16x16/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/16x16/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/16x16/apps
+icon_DATA = gnome-lirc-properties.png
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/16x16/gnome-lirc-properties.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/icons/22x22/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/22x22/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/22x22/apps
+icon_DATA = gnome-lirc-properties.png
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/22x22/gnome-lirc-properties.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/icons/24x24/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/24x24/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/24x24/apps
+icon_DATA = gnome-lirc-properties.png
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/24x24/gnome-lirc-properties.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/icons/32x32/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/32x32/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/32x32/apps
+icon_DATA = gnome-lirc-properties.png
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/32x32/gnome-lirc-properties.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/icons/48x48/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/48x48/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/48x48/apps
+icon_DATA = gnome-lirc-properties.png
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/48x48/gnome-lirc-properties.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/icons/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+SUBDIRS = 16x16 22x22 24x24 scalable #32x32 48x48

Added: trunk/data/icons/scalable/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/icons/scalable/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+icondir = $(datadir)/icons/hicolor/scalable/apps
+icon_DATA = gnome-lirc-properties.svg
+EXTRA_DIST = $(icon_DATA)

Added: trunk/data/icons/scalable/gnome-lirc-properties.svg
==============================================================================
--- (empty file)
+++ trunk/data/icons/scalable/gnome-lirc-properties.svg	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,627 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1+0.46pre1"
+   version="1.0"
+   sodipodi:docname="remote-control.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/misc icons/16x16/remote-control.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6116">
+      <stop
+         style="stop-color:#5a5a57;stop-opacity:1"
+         offset="0"
+         id="stop6118" />
+      <stop
+         style="stop-color:#a0a19d;stop-opacity:1"
+         offset="1"
+         id="stop6120" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6030">
+      <stop
+         style="stop-color:#90918c;stop-opacity:1"
+         offset="0"
+         id="stop6032" />
+      <stop
+         style="stop-color:#525250;stop-opacity:1"
+         offset="1"
+         id="stop6034" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6016">
+      <stop
+         style="stop-color:#888a85;stop-opacity:1"
+         offset="0"
+         id="stop6018" />
+      <stop
+         style="stop-color:#4c4d4a;stop-opacity:1"
+         offset="1"
+         id="stop6020" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="-50 : 600 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="700 : 600 : 1"
+       inkscape:persp3d-origin="300 : 400 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective5177"
+       inkscape:persp3d-origin="300 : 400 : 1"
+       inkscape:vp_z="700 : 600 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="-50 : 600 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5190"
+       inkscape:persp3d-origin="300 : 400 : 1"
+       inkscape:vp_z="700 : 600 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="-50 : 600 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6016"
+       id="linearGradient6022"
+       x1="7.4375"
+       y1="3.875"
+       x2="14"
+       y2="16.8125"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8181817,0,0,0.7894737,-1,-0.6842106)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6030"
+       id="radialGradient6036"
+       cx="10.119372"
+       cy="6.3472557"
+       fx="10.119372"
+       fy="6.3472557"
+       r="4.4320445"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8544208,-1.4731349e-2,1.7238766e-2,0.9998514,1.3588774,0.1517086)" />
+    <filter
+       inkscape:collect="always"
+       id="filter6092"
+       x="-0.063940093"
+       width="1.1278802"
+       y="-0.40921658"
+       height="1.8184332">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.37298387"
+         id="feGaussianBlur6094" />
+    </filter>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6016"
+       id="linearGradient6174"
+       gradientUnits="userSpaceOnUse"
+       x1="7.4375"
+       y1="3.875"
+       x2="14"
+       y2="16.8125"
+       gradientTransform="translate(-27,-0.9999999)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6030"
+       id="radialGradient6176"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8544208,-1.4731349e-2,1.7238766e-2,0.9998514,1.3588774,0.1517086)"
+       cx="10.119372"
+       cy="6.3472557"
+       fx="10.119372"
+       fy="6.3472557"
+       r="4.4320445" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6116"
+       id="linearGradient6178"
+       gradientUnits="userSpaceOnUse"
+       x1="11.21847"
+       y1="5.3545976"
+       x2="11.079462"
+       y2="11.277349" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="12.12284"
+     inkscape:cy="2.1074383"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-bbox="true"
+     inkscape:snap-nodes="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1007"
+     inkscape:window-height="706"
+     inkscape:window-x="158"
+     inkscape:window-y="110">
+    <inkscape:grid
+       type="xygrid"
+       id="grid5130"
+       visible="true"
+       enabled="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="opacity:1;fill:url(#linearGradient6022);fill-opacity:1;stroke:#2e3436;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5132"
+       width="9"
+       height="15"
+       x="3.5"
+       y="0.5"
+       rx="0.72783983"
+       ry="0.82999277" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:url(#radialGradient6036);fill-opacity:1;stroke:#2c3234;stroke-width:1.47734809;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path5134"
+       sodipodi:cx="11.522053"
+       sodipodi:cy="7.8262973"
+       sodipodi:rx="3.6933703"
+       sodipodi:ry="3.6933703"
+       d="M 15.215423,7.8262973 A 3.6933703,3.6933703 0 1 1 7.8286824,7.8262973 A 3.6933703,3.6933703 0 1 1 15.215423,7.8262973 z"
+       transform="matrix(0.6768885,0,0,0.6768885,0.200854,-1.2975309)" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5208"
+       width="2"
+       height="2"
+       x="5"
+       y="8"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5996"
+       width="1"
+       height="1"
+       x="5"
+       y="8"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:0.1220657;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6014"
+       width="7.0000005"
+       height="13"
+       x="4.5"
+       y="1.5"
+       rx="0.0024565242"
+       ry="0.0024152384" />
+    <rect
+       style="opacity:1;fill:#cc0000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6024"
+       width="2"
+       height="1"
+       x="7"
+       y="0"
+       rx="0.421875"
+       ry="0.421875" />
+    <rect
+       style="opacity:1;fill:#ef2929;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6026"
+       width="0.9818182"
+       height="1"
+       x="7.0181808"
+       y="0"
+       rx="0.33135545"
+       ry="0.33749166" />
+    <rect
+       style="opacity:0.35680751;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter6092)"
+       id="rect6124"
+       width="14"
+       height="2.1875"
+       x="-23"
+       y="18.3125"
+       rx="0.60815692"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:url(#linearGradient6174);fill-opacity:1;stroke:#2e3436;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6126"
+       width="11.000001"
+       height="19"
+       x="-21.5"
+       y="0.5"
+       rx="0.88958216"
+       ry="1.0513241" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:url(#radialGradient6176);fill-opacity:1;stroke:#384042;stroke-width:1.47734821;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path6128"
+       sodipodi:cx="11.522053"
+       sodipodi:cy="7.8262973"
+       sodipodi:rx="3.6933703"
+       sodipodi:ry="3.6933703"
+       d="M 15.215423,7.8262973 A 3.6933703,3.6933703 0 1 1 7.8286824,7.8262973 A 3.6933703,3.6933703 0 1 1 15.215423,7.8262973 z"
+       transform="matrix(0.6768885,0,0,0.6768885,-23.799145,-0.2975309)" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6130"
+       width="2"
+       height="2"
+       x="-20"
+       y="9.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6132"
+       width="2"
+       height="2"
+       x="-17"
+       y="9.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6134"
+       width="2"
+       height="2"
+       x="-14"
+       y="9.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6136"
+       width="2"
+       height="2"
+       x="-20"
+       y="12.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6138"
+       width="2"
+       height="2"
+       x="-17"
+       y="12.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6140"
+       width="2"
+       height="2"
+       x="-14"
+       y="12.999999"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6142"
+       width="2"
+       height="2"
+       x="-20"
+       y="16"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6144"
+       width="2"
+       height="2"
+       x="-17"
+       y="16"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6146"
+       width="2"
+       height="2"
+       x="-14"
+       y="16"
+       rx="0.63259178"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6148"
+       width="1"
+       height="1"
+       x="-20"
+       y="9.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6150"
+       width="1"
+       height="1"
+       x="-17"
+       y="9.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6152"
+       width="1"
+       height="1"
+       x="-14"
+       y="9.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6154"
+       width="1"
+       height="1"
+       x="-20"
+       y="12.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6156"
+       width="1"
+       height="1"
+       x="-17"
+       y="12.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6158"
+       width="1"
+       height="1"
+       x="-14"
+       y="12.999999"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6160"
+       width="1"
+       height="1"
+       x="-20"
+       y="16"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6162"
+       width="1"
+       height="1"
+       x="-17"
+       y="16"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6164"
+       width="1"
+       height="1"
+       x="-14"
+       y="16"
+       rx="0.39692032"
+       ry="0.3488968" />
+    <rect
+       style="opacity:0.1220657;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6166"
+       width="9.000001"
+       height="17"
+       x="-20.5"
+       y="1.5"
+       rx="0.0031583887"
+       ry="0.0031583887" />
+    <rect
+       style="opacity:1;fill:#cc0000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6168"
+       width="2"
+       height="1"
+       x="-17"
+       y="0"
+       rx="0.421875"
+       ry="0.421875" />
+    <rect
+       style="opacity:1;fill:#ef2929;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6170"
+       width="1"
+       height="1"
+       x="-17"
+       y="0"
+       rx="0.33749169"
+       ry="0.33749169" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:0.24413147;fill:none;fill-opacity:1;stroke:url(#linearGradient6178);stroke-width:1.05524874;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path6172"
+       sodipodi:cx="11.522053"
+       sodipodi:cy="7.8262973"
+       sodipodi:rx="3.6933703"
+       sodipodi:ry="3.6933703"
+       d="M 15.215423,7.8262973 A 3.6933703,3.6933703 0 1 1 7.8286824,7.8262973 A 3.6933703,3.6933703 0 1 1 15.215423,7.8262973 z"
+       transform="matrix(0.9476439,0,0,0.9476439,-26.918803,-2.4165433)" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6180"
+       width="2"
+       height="2"
+       x="7"
+       y="8"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6182"
+       width="1"
+       height="1"
+       x="7"
+       y="8"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6184"
+       width="2"
+       height="2"
+       x="9"
+       y="8"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6186"
+       width="1"
+       height="1"
+       x="9"
+       y="8"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6188"
+       width="2"
+       height="2"
+       x="5"
+       y="10"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6190"
+       width="1"
+       height="1"
+       x="5"
+       y="10"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6192"
+       width="2"
+       height="2"
+       x="7"
+       y="10"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6194"
+       width="1"
+       height="1"
+       x="7"
+       y="10"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6196"
+       width="2"
+       height="2"
+       x="9"
+       y="10"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6198"
+       width="1"
+       height="1"
+       x="9"
+       y="10"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6200"
+       width="2"
+       height="2"
+       x="5"
+       y="12"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6202"
+       width="1"
+       height="1"
+       x="5"
+       y="12"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6204"
+       width="2"
+       height="2"
+       x="7"
+       y="12"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6206"
+       width="1"
+       height="1"
+       x="7"
+       y="12"
+       rx="0.39692029"
+       ry="0.34889677" />
+    <rect
+       style="opacity:1;fill:#3a3b38;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6208"
+       width="2"
+       height="2"
+       x="9"
+       y="12"
+       rx="0.63259172"
+       ry="0.61056942" />
+    <rect
+       style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6210"
+       width="1"
+       height="1"
+       x="9"
+       y="12"
+       rx="0.39692029"
+       ry="0.34889677" />
+  </g>
+</svg>

Added: trunk/data/linux-input-layer-lircd.conf
==============================================================================
--- (empty file)
+++ trunk/data/linux-input-layer-lircd.conf	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,371 @@
+# LIRC configuration file for receivers with Linux Input Layer driver.
+# http://linux.bytesex.org/v4l2/linux-input-layer-lircd.conf
+#
+# brand: Generic
+# model: Linux Input Layer compatible Remote
+#
+
+begin remote
+	name linux-input-layer
+	bits 32
+	begin codes
+		ESC                  0x10001
+		1                    0x10002
+		2                    0x10003
+		3                    0x10004
+		4                    0x10005
+		5                    0x10006
+		6                    0x10007
+		7                    0x10008
+		8                    0x10009
+		9                    0x1000a
+		0                    0x1000b
+		MINUS                0x1000c
+		EQUAL                0x1000d
+		BACKSPACE            0x1000e
+		TAB                  0x1000f
+		Q                    0x10010
+		W                    0x10011
+		E                    0x10012
+		R                    0x10013
+		T                    0x10014
+		Y                    0x10015
+		U                    0x10016
+		I                    0x10017
+		O                    0x10018
+		P                    0x10019
+		LEFTBRACE            0x1001a
+		RIGHTBRACE           0x1001b
+		ENTER                0x1001c
+		LEFTCTRL             0x1001d
+		A                    0x1001e
+		S                    0x1001f
+		D                    0x10020
+		F                    0x10021
+		G                    0x10022
+		H                    0x10023
+		J                    0x10024
+		K                    0x10025
+		L                    0x10026
+		SEMICOLON            0x10027
+		APOSTROPHE           0x10028
+		GRAVE                0x10029
+		LEFTSHIFT            0x1002a
+		BACKSLASH            0x1002b
+		Z                    0x1002c
+		X                    0x1002d
+		C                    0x1002e
+		V                    0x1002f
+		B                    0x10030
+		N                    0x10031
+		M                    0x10032
+		COMMA                0x10033
+		DOT                  0x10034
+		SLASH                0x10035
+		RIGHTSHIFT           0x10036
+		KPASTERISK           0x10037
+		LEFTALT              0x10038
+		SPACE                0x10039
+		CAPSLOCK             0x1003a
+		F1                   0x1003b
+		F2                   0x1003c
+		F3                   0x1003d
+		F4                   0x1003e
+		F5                   0x1003f
+		F6                   0x10040
+		F7                   0x10041
+		F8                   0x10042
+		F9                   0x10043
+		F10                  0x10044
+		NUMLOCK              0x10045
+		SCROLLLOCK           0x10046
+		KP7                  0x10047
+		KP8                  0x10048
+		KP9                  0x10049
+		KPMINUS              0x1004a
+		KP4                  0x1004b
+		KP5                  0x1004c
+		KP6                  0x1004d
+		KPPLUS               0x1004e
+		KP1                  0x1004f
+		KP2                  0x10050
+		KP3                  0x10051
+		KP0                  0x10052
+		KPDOT                0x10053
+		103RD                0x10054
+		F13                  0x10055
+		102ND                0x10056
+		F11                  0x10057
+		F12                  0x10058
+		F14                  0x10059
+		F15                  0x1005a
+		F16                  0x1005b
+		F17                  0x1005c
+		F18                  0x1005d
+		F19                  0x1005e
+		F20                  0x1005f
+		KPENTER              0x10060
+		RIGHTCTRL            0x10061
+		KPSLASH              0x10062
+		SYSRQ                0x10063
+		RIGHTALT             0x10064
+		LINEFEED             0x10065
+		HOME                 0x10066
+		UP                   0x10067
+		PAGEUP               0x10068
+		LEFT                 0x10069
+		RIGHT                0x1006a
+		END                  0x1006b
+		DOWN                 0x1006c
+		PAGEDOWN             0x1006d
+		INSERT               0x1006e
+		DELETE               0x1006f
+		MACRO                0x10070
+		MUTE                 0x10071
+		VOLUMEDOWN           0x10072
+		VOLUMEUP             0x10073
+		POWER                0x10074
+		KPEQUAL              0x10075
+		KPPLUSMINUS          0x10076
+		PAUSE                0x10077
+		F21                  0x10078
+		F22                  0x10079
+		F23                  0x1007a
+		F24                  0x1007b
+		KPCOMMA              0x1007c
+		LEFTMETA             0x1007d
+		RIGHTMETA            0x1007e
+		COMPOSE              0x1007f
+		STOP                 0x10080
+		AGAIN                0x10081
+		PROPS                0x10082
+		UNDO                 0x10083
+		FRONT                0x10084
+		COPY                 0x10085
+		OPEN                 0x10086
+		PASTE                0x10087
+		FIND                 0x10088
+		CUT                  0x10089
+		HELP                 0x1008a
+		MENU                 0x1008b
+		CALC                 0x1008c
+		SETUP                0x1008d
+		SLEEP                0x1008e
+		WAKEUP               0x1008f
+		FILE                 0x10090
+		SENDFILE             0x10091
+		DELETEFILE           0x10092
+		XFER                 0x10093
+		PROG1                0x10094
+		PROG2                0x10095
+		WWW                  0x10096
+		MSDOS                0x10097
+		COFFEE               0x10098
+		DIRECTION            0x10099
+		CYCLEWINDOWS         0x1009a
+		MAIL                 0x1009b
+		BOOKMARKS            0x1009c
+		COMPUTER             0x1009d
+		BACK                 0x1009e
+		FORWARD              0x1009f
+		CLOSECD              0x100a0
+		EJECTCD              0x100a1
+		EJECTCLOSECD         0x100a2
+		NEXTSONG             0x100a3
+		PLAYPAUSE            0x100a4
+		PREVIOUSSONG         0x100a5
+		STOPCD               0x100a6
+		RECORD               0x100a7
+		REWIND               0x100a8
+		PHONE                0x100a9
+		ISO                  0x100aa
+		CONFIG               0x100ab
+		HOMEPAGE             0x100ac
+		REFRESH              0x100ad
+		EXIT                 0x100ae
+		MOVE                 0x100af
+		EDIT                 0x100b0
+		SCROLLUP             0x100b1
+		SCROLLDOWN           0x100b2
+		KPLEFTPAREN          0x100b3
+		KPRIGHTPAREN         0x100b4
+		INTL1                0x100b5
+		INTL2                0x100b6
+		INTL3                0x100b7
+		INTL4                0x100b8
+		INTL5                0x100b9
+		INTL6                0x100ba
+		INTL7                0x100bb
+		INTL8                0x100bc
+		INTL9                0x100bd
+		LANG1                0x100be
+		LANG2                0x100bf
+		LANG3                0x100c0
+		LANG4                0x100c1
+		LANG5                0x100c2
+		LANG6                0x100c3
+		LANG7                0x100c4
+		LANG8                0x100c5
+		LANG9                0x100c6
+		PLAYCD               0x100c8
+		PAUSECD              0x100c9
+		PROG3                0x100ca
+		PROG4                0x100cb
+		SUSPEND              0x100cd
+		CLOSE                0x100ce
+		PLAY                 0x100cf
+		FASTFORWARD          0x100d0
+		BASSBOOST            0x100d1
+		PRINT                0x100d2
+		HP                   0x100d3
+		CAMERA               0x100d4
+		SOUND                0x100d5
+		QUESTION             0x100d6
+		EMAIL                0x100d7
+		CHAT                 0x100d8
+		SEARCH               0x100d9
+		CONNECT              0x100da
+		FINANCE              0x100db
+		SPORT                0x100dc
+		SHOP                 0x100dd
+		ALTERASE             0x100de
+		CANCEL               0x100df
+		BRIGHTNESSDOWN       0x100e0
+		BRIGHTNESSUP         0x100e1
+		MEDIA                0x100e2
+		UNKNOWN              0x100f0
+		BTN_MISC             0x10100
+		BTN_0                0x10100
+		BTN_1                0x10101
+		BTN_2                0x10102
+		BTN_3                0x10103
+		BTN_4                0x10104
+		BTN_5                0x10105
+		BTN_6                0x10106
+		BTN_7                0x10107
+		BTN_8                0x10108
+		BTN_9                0x10109
+		BTN_MOUSE            0x10110
+		BTN_LEFT             0x10110
+		BTN_RIGHT            0x10111
+		BTN_MIDDLE           0x10112
+		BTN_SIDE             0x10113
+		BTN_EXTRA            0x10114
+		BTN_FORWARD          0x10115
+		BTN_BACK             0x10116
+		BTN_TASK             0x10117
+		BTN_JOYSTICK         0x10120
+		BTN_TRIGGER          0x10120
+		BTN_THUMB            0x10121
+		BTN_THUMB2           0x10122
+		BTN_TOP              0x10123
+		BTN_TOP2             0x10124
+		BTN_PINKIE           0x10125
+		BTN_BASE             0x10126
+		BTN_BASE2            0x10127
+		BTN_BASE3            0x10128
+		BTN_BASE4            0x10129
+		BTN_BASE5            0x1012a
+		BTN_BASE6            0x1012b
+		BTN_DEAD             0x1012f
+		BTN_GAMEPAD          0x10130
+		BTN_A                0x10130
+		BTN_B                0x10131
+		BTN_C                0x10132
+		BTN_X                0x10133
+		BTN_Y                0x10134
+		BTN_Z                0x10135
+		BTN_TL               0x10136
+		BTN_TR               0x10137
+		BTN_TL2              0x10138
+		BTN_TR2              0x10139
+		BTN_SELECT           0x1013a
+		BTN_START            0x1013b
+		BTN_MODE             0x1013c
+		BTN_THUMBL           0x1013d
+		BTN_THUMBR           0x1013e
+		BTN_DIGI             0x10140
+		BTN_TOOL_PEN         0x10140
+		BTN_TOOL_RUBBER      0x10141
+		BTN_TOOL_BRUSH       0x10142
+		BTN_TOOL_PENCIL      0x10143
+		BTN_TOOL_AIRBRUSH    0x10144
+		BTN_TOOL_FINGER      0x10145
+		BTN_TOOL_MOUSE       0x10146
+		BTN_TOOL_LENS        0x10147
+		BTN_TOUCH            0x1014a
+		BTN_STYLUS           0x1014b
+		BTN_STYLUS2          0x1014c
+		BTN_WHEEL            0x10150
+		BTN_GEAR_DOWN        0x10150
+		BTN_GEAR_UP          0x10151
+		OK                   0x10160
+		SELECT               0x10161
+		GOTO                 0x10162
+		CLEAR                0x10163
+		POWER2               0x10164
+		OPTION               0x10165
+		INFO                 0x10166
+		TIME                 0x10167
+		VENDOR               0x10168
+		ARCHIVE              0x10169
+		PROGRAM              0x1016a
+		CHANNEL              0x1016b
+		FAVORITES            0x1016c
+		EPG                  0x1016d
+		PVR                  0x1016e
+		MHP                  0x1016f
+		LANGUAGE             0x10170
+		TITLE                0x10171
+		SUBTITLE             0x10172
+		ANGLE                0x10173
+		ZOOM                 0x10174
+		MODE                 0x10175
+		KEYBOARD             0x10176
+		SCREEN               0x10177
+		PC                   0x10178
+		TV                   0x10179
+		TV2                  0x1017a
+		VCR                  0x1017b
+		VCR2                 0x1017c
+		SAT                  0x1017d
+		SAT2                 0x1017e
+		CD                   0x1017f
+		TAPE                 0x10180
+		RADIO                0x10181
+		TUNER                0x10182
+		PLAYER               0x10183
+		TEXT                 0x10184
+		DVD                  0x10185
+		AUX                  0x10186
+		MP3                  0x10187
+		AUDIO                0x10188
+		VIDEO                0x10189
+		DIRECTORY            0x1018a
+		LIST                 0x1018b
+		MEMO                 0x1018c
+		CALENDAR             0x1018d
+		RED                  0x1018e
+		GREEN                0x1018f
+		YELLOW               0x10190
+		BLUE                 0x10191
+		CHANNELUP            0x10192
+		CHANNELDOWN          0x10193
+		FIRST                0x10194
+		LAST                 0x10195
+		AB                   0x10196
+		NEXT                 0x10197
+		RESTART              0x10198
+		SLOW                 0x10199
+		SHUFFLE              0x1019a
+		BREAK                0x1019b
+		PREVIOUS             0x1019c
+		DIGITS               0x1019d
+		TEEN                 0x1019e
+		TWEN                 0x1019f
+		DEL_EOL              0x101c0
+		DEL_EOS              0x101c1
+		INS_LINE             0x101c2
+		DEL_LINE             0x101c3
+	end codes
+end remote

Added: trunk/data/org.gnome.LircProperties.Mechanism.conf
==============================================================================
--- (empty file)
+++ trunk/data/org.gnome.LircProperties.Mechanism.conf	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
+
+<!--
+  Permissions for the configuration backend of gnome-lirc-properties.
+  -->
+
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
+<busconfig>
+  <!--
+    The root user must be able to own this service. Don't forget to update
+    /usr/share/dbus-1/system-services/org.gnome.LircProperties.Mechanism.service
+    when you believe, that some other user should own this service.
+    -->
+  <policy user="root">
+    <allow own="org.gnome.LircProperties.Mechanism"/>
+  </policy>
+</busconfig>

Added: trunk/data/org.gnome.LircProperties.Mechanism.service.in
==============================================================================
--- (empty file)
+++ trunk/data/org.gnome.LircProperties.Mechanism.service.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,12 @@
+# Service description of the configuration backend of gnome-lirc-properties.
+# The service must be run as root for changing LIRC configuration and for
+# managing the LIRC daemon.
+#
+# IMPORTANT NOTICE: You have to update the service's configuration file
+# /etc/dbus-1/system.d/org.gnome.LircProperties.Mechanism.conf when feeling
+# adventurous and trying other user ids for this service.
+#
+[D-BUS Service]
+Name=org.gnome.LircProperties.Mechanism
+Exec= PYTHON@ -m gnome_lirc_properties.backend
+User=root

Added: trunk/data/overrides.conf
==============================================================================
--- (empty file)
+++ trunk/data/overrides.conf	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,80 @@
+[Creative Technology: SoundBlaster Remote Control Upgrade Kit]
+compatible-remotes = Creative_RM-1800, Creative_RM-1500
+kernel-module      = usbhid
+lirc-driver        = sb0540
+vendor-id          = 0x041e
+product-id         = 0x3100
+
+[Generic: Serial Port Receiver]
+kernel-module:      lirc_serial
+device-nodes:       hal-capability:serial
+
+[Generic: UDP Port Listener]
+lirc-driver:        udp
+device-nodes:       numeric:8765:1:65535:UDP-_Port
+
+[USB-Overrides]
+bad-vendor-tokens:  VENDOR_ATI1
+
+cmdir-vendor:       Cygnal
+cmdir-product:      CommandIR USB Transceiver
+imon-product:       iMON Remote Controller
+ms-vendor:          Microsoft Corp.
+mceusb-vendor:      Microsoft Corp.
+mceusb-product:     MCE Remote
+
+03eb-0002-product:  IgorPlugUSB receiver
+03eb-0002-vendor:   Homebrew
+03ee-2501-product:  eHome Infrared Transciever
+040b-6521-product:  Xbox DVD Movie Playback Kit IR
+040b-6521-vendor:   Gamester
+043e-9803-product:  eHome Infrared Transceiver
+045e-00a0-product:  MCE Infrared Transceiver
+0471-0602-product:  Remote Wonder 2 (Input Device)
+0471-0602-vendor:   ATI Technologies, Inc.
+0471-0603-product:  Remote Wonder 2 (Controller)
+0471-0603-vendor:   ATI Technologies, Inc.
+0471-060c-product:  eHome Infrared Transciever
+0471-060c-vendor:   Hewlett-Packard
+0471-0815-product:  eHome Infrared Transciever
+0609-031d-product:  G83C0004D410
+0609-031d-vendor:   Toshiba
+0609-0322-product:  VAIO eHome Infrared Transciever
+0609-0322-vendor:   Sony
+0609-0334-product:  PVR-150 Infrared Transciever
+0609-0334-vendor:   Hauppauge
+0b48-2003-product:  USB Infrared Receiver
+0bc7-0002-product:  USB Firecracker Interface
+0bc7-0003-product:  VGA Video Sender
+0bc7-0004-vendor:   ATI Technologies, Inc.
+0bc7-0005-product:  Wireless Remote Receiver
+0bc7-0005-vendor:   NVidia Corp.
+0bc7-0006-product:  Receiver
+0bc7-0006-vendor:   ATI Technologies, Inc.
+0bc7-0007-product:  USB Wireless Transceiver
+0bc7-0008-product:  Firefly PC Remote
+0bc7-0008-remotes:  Firefly_R1000
+0bc7-0008-vendor:   SnapStream Media
+0bc7-0009-product:  USB Wireless Transceiver
+0bc7-000a-product:  USB Wireless Transceiver
+0bc7-000b-product:  USB Wireless Transceiver
+0bc7-000c-product:  USB Wireless Transceiver
+0bc7-000d-product:  USB Wireless Transceiver
+0bc7-000e-product:  USB Wireless Transceiver
+0bc7-000f-product:  USB Wireless Transceiver
+0e9c-0000-remotes:  Streamzap_PC_Remote
+107b-3009-product:  eHome Infrared Transciever
+11ba-0101-product:  OnAir VFD/IR USB
+11ba-0101-vendor:   Sasem
+1308-c001-product:  eHome Infrared Transciever
+1460-9150-product:  eHome Infrared Transciever
+147a-e015-product:  eHome Infrared Transceiver
+1509-9242-product:  eHome Infrared Transceiver
+1784-0001-product:  eHome Infrared Transciever
+1784-0006-product:  eHome Infrared Transciever
+1784-0006-vendor:   Hewlett-Packard
+1784-0008-product:  eHome Infrared Transciever
+179d-0010-product:  Internal Infrared Transceiver
+1934-1934-product:  eHome Infrared Transceiver
+195d-7002-product:  Libra Q-11
+

Added: trunk/data/receivers.conf
==============================================================================
--- (empty file)
+++ trunk/data/receivers.conf	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,73 @@
+# Local version of the LIRC Hardware DataBase, providing information that
+# is not in the regular version, or is not correct in the regular version.
+#
+# The regular version of this file is normally found
+# at /usr/share/lirc/lirc.hwdb
+# and appears to be only in Debian/Ubuntu.
+#
+# This currently uses key/value though lirc.hwdb uses a comma-separated list:
+#
+#       [remote controls type]
+#       description;driver;lirc driver;HW_DEFAULT;lircd_conf;
+#
+# Reasons for this discrepancy are:
+#
+#       * Model and vendor names are not separated in lirc.hwdb.
+#         Many entries in lirc.hwdb don't even have vendor information.
+#
+#       * Comma-separated list are not very future proof.
+#         Adding new fields can break existing applications.
+#
+# So for now we keep our information here, in a human and machine friendly file
+# format, which is easy to extend.
+#
+# TODO: Talk to the lirc maintainers regarding the hardware database,
+#       once this file is know to be feature complete.
+#       Though this may be a Debian/Ubuntu-only file.
+#
+[Creative Technology: SoundBlaster Remote Control Upgrade Kit]
+compatible-remotes = Creative_RM-1800, Creative_RM-1500
+kernel-module      = usbhid
+lirc-driver        = sb0540
+vendor-id          = 0x041e
+product-id         = 0x3100
+
+[SnapStream Media: Firefly PC Remote]
+compatible-remotes = Snapstream_Firefly_R1000
+kernel-module      = lirc_atiusb
+lirc-driver        = default
+vendor-id          = 0x0bc7
+product-id         = 0x0008
+
+[StreamZap: PC Remote]
+compatible-remotes = Streamzap_PC_Remote
+kernel-module      = lirc_streamzap
+lirc-driver        = default
+vendor-id          = 0x0e9c
+product-id         = 0x0000
+
+# TODO: This is not yet actually supported by lirc, as of 6th February 2008,
+# though it is rumoured to work with a patched lirc_mceusb2
+[Pinnacle: Remote Kit]
+kernel-module = lirc_mceusb2
+lirc-driver   = default
+vendor-id     = 0x2304
+product-id    = 0x0225
+
+#[Sony: 4-Device Universal Remote Commander (RM-V202)]
+#kernel-module  = lirc_streamzap
+
+#[Microsoft: Xbox 360 Universal Media Remote]
+#kernel-module  = lirc_atiusb
+
+#[One For All: 4-DEVICE Universal Big-button Remote]
+
+[Generic: Serial Port Receiver]
+kernel-module = lirc_serial
+device-nodes  = hal-capability:serial
+
+[Generic: UDP Port Listener]
+lirc-driver  = udp
+device-nodes = numeric:8765:1:65535:UDP-_Port
+
+

Added: trunk/debian/changelog
==============================================================================
--- (empty file)
+++ trunk/debian/changelog	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,5 @@
+gnome-lirc-properties (0.2.5-0ubuntu1) hardy; urgency=low
+
+  * Initial upload to Ubuntu (LP: #192368).
+
+ -- Mathias Hasselmann <mathias hasselmann gmx de>  Fri, 19 Mar 2008 21:20:19 +0100

Added: trunk/debian/compat
==============================================================================
--- (empty file)
+++ trunk/debian/compat	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+5

Added: trunk/debian/control
==============================================================================
--- (empty file)
+++ trunk/debian/control	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,16 @@
+Source: gnome-lirc-properties
+Section: gnome
+Priority: optional
+Maintainer: Ubuntu MOTU Developers <ubuntu-motu lists ubuntu com>
+XSBC-Original-Maintainer: Openismus Package Team <ubuntu-packages lists openismus com>
+Build-Depends: cdbs, debhelper (>= 5), gnome-doc-utils, libpolkit-gnome-dev, lirc, pkg-config, python-support, scrollkeeper
+Standards-Version: 3.7.3
+Homepage: https://code.fluendo.com/remotecontrol/trac/
+
+Package: gnome-lirc-properties
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, lirc, policykit-gnome (>= 0.7), python-gtk2, yelp
+Description: Control panel to configure remote controls
+ This control panel updates lirc's configuration files to match your choices.
+ It also allows editing, uploading and downloading of custom remote control
+ configuration files.

Added: trunk/debian/copyright
==============================================================================
--- (empty file)
+++ trunk/debian/copyright	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,29 @@
+This package was debianized by Murray Cumming <murrayc murrayc com> and
+Mathias Hasselmann <mathias openismus com> on Sun, 10 Feb 2008 02:44:14 +0100.
+
+It was downloaded from http://download.gnome.org/sources/gnome-lirc-properties/
+
+Upstream Authors:
+
+    Murray Cumming <murrayc murrayc com>
+    Mathias Hasselmann <mathias openismus com>
+
+Copyright:
+
+Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.
+
+On Debian GNU/Linux systems, the full text of the GNU General Public
+License can be found in the file /usr/share/common-licenses/GPL.
+
+The Debian packaging is (C) 2008, Murray Cumming <murrayc murrayc com>
+and is licensed under the GPL, see `/usr/share/common-licenses/GPL'.

Added: trunk/debian/docs
==============================================================================
--- (empty file)
+++ trunk/debian/docs	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,3 @@
+NEWS
+README
+TODO

Added: trunk/debian/prerm
==============================================================================
--- (empty file)
+++ trunk/debian/prerm	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,24 @@
+#!/bin/sh
+# prerm script for gnome-lirc-properties
+#
+# see: dh_installdeb(1)
+
+set -e
+
+case "$1" in
+    remove|deconfigure)
+        rm -f /usr/share/gnome-lirc-properties/remotes.tar.gz
+    ;;
+
+    upgrade|failed-upgrade)
+    ;;
+
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+#DEBHELPER#
+
+exit 0

Added: trunk/debian/pyversions
==============================================================================
--- (empty file)
+++ trunk/debian/pyversions	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+2.4-

Added: trunk/debian/rules
==============================================================================
--- (empty file)
+++ trunk/debian/rules	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,10 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/class/gnome.mk
+
+install/gnome-lirc-properties::
+	dh_pysupport
+
+binary-install/gnome-lirc-properties::
+	rmdir debian/gnome-lirc-properties/usr/share/locale

Added: trunk/debian/watch
==============================================================================
--- (empty file)
+++ trunk/debian/watch	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,2 @@
+version=3
+http://download.gnome.org/sources/gnome-lirc-properties/([\d\.]+)/gnome-lirc-properties-(.*)\.tar\.gz

Added: trunk/doc/lirc.dia
==============================================================================
Binary file. No diff available.

Added: trunk/doc/lirc.txt
==============================================================================
--- (empty file)
+++ trunk/doc/lirc.txt	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,144 @@
+Hardware Modules
+================
+
+hw_accent
+hw_alsa_usb
+hw_audio_alsa
+hw_bte
+hw_creative
+hw_creative_infracd
+hw_default
+hw_devinput
+hw_dsp
+hw_ea65
+hw_hiddev
+hw_i2cuser
+hw_livedrive_common
+hw_livedrive_midi
+hw_livedrive_seq
+hw_logitech
+hw_mouseremote
+hw_mp3anywhere
+hw_pcmak
+hw_pinsys
+hw_pixelview
+hw_silitek
+hw_tira
+hw_udp
+hw_uirt2
+hw_uirt2_common
+hw_uirt2_raw
+hw_usbx
+receive
+serial
+transmit
+
+Kernel Drivers
+==============
+
+lirc_dev: LIRC base driver module
+
+  char-major-61-*
+
+lirc_atiusb: USB remote driver for LIRC
+
+  usb:v045Ep0284d*dc*dsc*dp*ic*isc*ip*
+  usb:v040Bp6521d*dc*dsc*dp*ic*isc*ip*
+  usb:v0471p0603d*dc*dsc*dp*ic*isc*ip*
+  usb:v0471p0602d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Fd*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Ed*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Dd*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Cd*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Bd*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p000Ad*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0009d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0008d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0007d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0006d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0005d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0004d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0003d*dc*dsc*dp*ic*isc*ip*
+  usb:v0BC7p0002d*dc*dsc*dp*ic*isc*ip*
+
+lirc_bt829: IR remote driver for bt829 based TV cards
+
+  no module aliases
+
+lirc_cmdir: InnovationOne driver for CommandIR USB infrared transceiver
+
+  no module aliases
+
+lirc_gpio: Driver module for remote control (data from bt848 GPIO port)
+
+    Not available on Ubuntu Gutsy
+
+lirc_i2c: Infrared receiver driver for Hauppauge and Pixelview cards (i2c stack)
+
+  no module aliases
+
+lirc_igorplugusb: USB remote driver for LIRC
+
+  usb:v03EBp0002d*dc*dsc*dp*ic*isc*ip*
+
+lirc_imon: Driver for Soundgraph iMON MultiMedian IR/VFD
+
+  usb:v04E8pFF30d*dc*dsc*dp*ic*isc*ip*
+  usb:v15C2pFFDCd*dc*dsc*dp*ic*isc*ip*
+  usb:v15C2pFFDAd*dc*dsc*dp*ic*isc*ip*
+  usb:v0AA8p8001d*dc*dsc*dp*ic*isc*ip*
+  usb:v0AA8pFFDAd*dc*dsc*dp*ic*isc*ip*
+
+lirc_it87: LIRC driver for ITE IT8712/IT8705 CIR port
+
+  no module aliases
+
+lirc_mceusb: USB Microsoft IR Transceiver Driver
+
+  usb:v045Ep006Dd*dc*dsc*dp*ic*isc*ip*
+
+lirc_mceusb2: Philips eHome USB IR Transciever and Microsoft MCE 2005 Remote Control driver for LIRC
+
+  usb:v147ApE015d*dc*dsc*dp*ic*isc*ip*
+  usb:v045Ep00A0d*dc*dsc*dp*ic*isc*ip*
+  usb:v043Ep9803d*dc*dsc*dp*ic*isc*ip*
+  usb:v1509p9242d*dc*dsc*dp*ic*isc*ip*
+  usb:v195Dp7002d*dc*dsc*dp*ic*isc*ip*
+  usb:v179Dp0010d*dc*dsc*dp*ic*isc*ip*
+  usb:v1784p0008d*dc*dsc*dp*ic*isc*ip*
+  usb:v1784p0006d*dc*dsc*dp*ic*isc*ip*
+  usb:v1784p0001d*dc*dsc*dp*ic*isc*ip*
+  usb:v03EEp2501d*dc*dsc*dp*ic*isc*ip*
+  usb:v107Bp3009d*dc*dsc*dp*ic*isc*ip*
+  usb:v1308pC001d*dc*dsc*dp*ic*isc*ip*
+  usb:v1460p9150d*dc*dsc*dp*ic*isc*ip*
+  usb:v0609p0334d*dc*dsc*dp*ic*isc*ip*
+  usb:v0609p0322d*dc*dsc*dp*ic*isc*ip*
+  usb:v0609p031Dd*dc*dsc*dp*ic*isc*ip*
+  usb:v0471p060Cd*dc*dsc*dp*ic*isc*ip*
+  usb:v0471p0815d*dc*dsc*dp*ic*isc*ip*
+
+lirc_parallel:
+
+  no module aliases
+
+lirc_sasem: USB Driver for Sasem Remote Controller V1.1
+
+  no module aliases
+
+lirc_serial: Infra-red receiver driver for serial ports.
+
+  no module aliases
+
+lirc_sir: Infrared receiver driver for SIR type serial ports
+
+  no module aliases
+
+lirc_streamzap: Streamzap Remote Control driver
+
+  usb:v0E9Cp0000d*dc*dsc*dp*ic*isc*ip*
+
+lirc_ttusbir: TechnoTrend USB IR device driver for LIRC
+
+  usb:v0B48p2003d*dc*dsc*dp*ic*isc*ip*
+

Added: trunk/gnome_lirc_properties/.gitignore
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/.gitignore	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,2 @@
+*.pyc
+config.py

Added: trunk/gnome_lirc_properties/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,14 @@
+SUBDIRS = net ui
+
+moduledir = $(pythondir)/gnome_lirc_properties
+
+module_PYTHON = \
+	__init__.py \
+	backend.py \
+	config.py \
+	hardware.py \
+	lirc.py \
+	lsb.py \
+	model.py \
+	policykit.py
+

Added: trunk/gnome_lirc_properties/__init__.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/__init__.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,57 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Infrared Remote Control Properties for GNOME.
+'''
+
+def run(args, datadir):
+    '''
+    Executes the properties dialog.
+    '''
+
+    import logging
+
+    # Support full tracing when --debug switch is passed:
+    if '--debug' in args or '-d' in args:
+        logging.getLogger().setLevel(logging.NOTSET)
+
+    # Integrate DBus with GLib main loop
+    from dbus.mainloop.glib import DBusGMainLoop
+    DBusGMainLoop(set_as_default=True)
+
+    # Initialize user interface
+    import pygtk
+
+    pygtk.require('2.0')
+
+    from gettext               import gettext as _
+    from gnome_lirc_properties import ui
+
+    import gobject, gtk, gtk.gdk, gtk.glade, os.path
+
+    # Setup defaut properties:
+    gobject.threads_init()
+    gobject.set_application_name(_('Infrared Remote Control Properties'))
+    gtk.window_set_default_icon_name('gnome-lirc-properties')
+
+    # Enable thread support:
+    gtk.gdk.threads_init()
+
+    # Load the user interface:
+    ui_filename = os.path.join(datadir, 'gnome-lirc-properties.glade')
+    return ui.RemoteControlProperties(gtk.glade.XML(ui_filename)).run()

Added: trunk/gnome_lirc_properties/backend.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/backend.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,852 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''Facilities for handling the D-BUS driven configuration backend.'''
+
+import dbus, dbus.service, gobject, logging, shutil
+import errno, os, os.path, re, pty, signal, tempfile
+
+from gettext               import gettext as _
+from gnome_lirc_properties import config, lirc
+from StringIO              import StringIO
+
+# Modern flavors of dbus bindings have that symbol in dbus.lowlevel,
+# for old flavours the internal _dbus_bindings module must be used.
+
+try:
+    # pylint: disable-msg=E0611
+    from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
+
+except ImportError:
+    from _dbus_bindings import HANDLER_RESULT_NOT_YET_HANDLED
+
+class AccessDeniedException(dbus.DBusException):
+    '''This exception is raised when some operation is not permitted.'''
+
+    _dbus_error_name = 'org.gnome.LircProperties.AccessDeniedException'
+
+class UnsupportedException(dbus.DBusException):
+    '''This exception is raised when some operation is not supported.'''
+
+    _dbus_error_name = 'org.gnome.LircProperties.UnsupportedException'
+
+class UsageError(dbus.DBusException):
+    '''This exception is raised when some operation was not used properly.'''
+
+    _dbus_error_name = 'org.gnome.LircProperties.UsageError'
+
+class PolicyKitService(dbus.service.Object):
+    '''A D-BUS service that uses PolicyKit for authorization.'''
+
+    def _check_permission(self, sender, action=config.POLICY_KIT_ACTION):
+        '''
+        Verifies if the specified action is permitted, and raises
+        an AccessDeniedException if not.
+
+        The caller should use ObtainAuthorization() to get permission.
+        '''
+
+        try:
+            if sender:
+                kit = dbus.SystemBus().get_object('org.freedesktop.PolicyKit', '/')
+                kit = dbus.Interface(kit, 'org.freedesktop.PolicyKit')
+                pid = dbus.UInt32(os.getpid())
+
+                granted = kit.IsProcessAuthorized(action, pid, False)
+                logging.info('process authorization: %r', granted)
+
+                if 'no' == granted:
+                    raise AccessDeniedException('Process not authorized by PolicyKit')
+
+                granted = kit.IsSystemBusNameAuthorized(action, sender, False)
+                logging.info('authorizatoin of system bus name: %r', granted)
+
+                if 'no' == granted:
+                    raise AccessDeniedException('Session not authorized by PolicyKit')
+
+        except AccessDeniedException:
+            raise
+
+        except dbus.DBusException, ex:
+            raise AccessDeniedException(ex.message)
+
+class ExternalToolDriver(PolicyKitService):
+    '''
+    A D-BUS service which mainly is implemented
+    as wrapper around some external program.
+    '''
+
+    # pylint: disable-msg=C0103,E0602
+
+    INTERFACE_NAME = 'org.gnome.LircProperties.ExternalToolDriver'
+
+    def __init__(self, connection, path='/'):
+        super(ExternalToolDriver, self).__init__(connection, path)
+
+        self.__line_buffer = ''
+        self.__pid = -1
+        self.__fd = -1
+
+    def _spawn_external_tool(self):
+        '''Launches the external tool backing this service.'''
+
+        pid, fd = pty.fork()
+
+        if 0 == pid:
+            self._on_run_external_tool()
+            assert False # should not be reached
+
+        os.waitpid(pid, os.P_NOWAIT)
+        return pid, fd
+
+    def __io_handler(self, fd, condition):
+        '''Handles I/O events related to the backing external tool.'''
+
+        if condition & gobject.IO_IN:
+            # Read next chunk for the file descriptor and buffer it:
+            chunk = os.read(fd, 4096)
+            self._on_next_chunk(chunk)
+            self.__line_buffer += chunk
+
+            # Extract complete lines from buffer:
+            while True:
+                linebreak = self.__line_buffer.find('\n')
+
+                # Abort, since the next line isn't complete yet:
+                if linebreak < 0:
+                    break
+
+                # Extract next line and normalize it:
+                line = self.__line_buffer[:linebreak].rstrip()
+                self.__line_buffer = self.__line_buffer[linebreak + 1:]
+
+                # Handle next line:
+                self._on_next_line(line)
+
+        if condition & gobject.IO_HUP:
+            # Shutdown the service, when the backing tool terminates:
+            self._on_hangup()
+            return False
+
+        # Keep the handler alive:
+        return True
+
+    def _send_response(self, response='\n'):
+        '''Sends a response, by default just a line feed, to the external tool.'''
+
+        os.write(self.__fd, response)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='', out_signature='',
+                         sender_keyword='sender')
+    def Execute(self, sender=None):
+        '''Requests the service to launch the backing tool.'''
+
+        self._check_permission(sender)
+
+        self.__line_buffer = ''
+        self.__pid, self.__fd = self._spawn_external_tool()
+
+        if -1 != self.__fd:
+            gobject.io_add_watch(self.__fd,
+                                 gobject.IO_IN | gobject.IO_HUP,
+                                 self.__io_handler)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='', out_signature='',
+                         sender_keyword='sender')
+    def Proceed(self, sender=None):
+        '''Requests the service to send a line feed to the backing tool.'''
+
+        self._check_permission(sender)
+        self._send_response('\n')
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='', out_signature='',
+                         sender_keyword='sender')
+    def Release(self, sender=None):
+        '''Releases the service and shuts down the backing tool, when still alive.'''
+
+        self._check_permission(sender)
+
+        try:
+            if -1 != os.waitpid(self.__pid, os.P_NOWAIT):
+                print 'Terminating child process %d...' % self.__pid
+                os.kill(self.__pid, signal.SIGTERM)
+
+        except OSError, ex:
+            if ex.errno != errno.ESRCH:
+                print 'Cannot terminate process %d: %s' % (self.__pid, ex.message)
+
+        self.__pid = 0
+        self.remove_from_connection()
+
+    @dbus.service.signal(dbus_interface=INTERFACE_NAME, signature='')
+    def ReportProgress(self):
+        '''Signals that the backing tool reported some progress.'''
+
+    @dbus.service.signal(dbus_interface=INTERFACE_NAME, signature='s')
+    def ReportSuccess(self, message):
+        '''Signals that the backing tool reported sucess.'''
+
+    @dbus.service.signal(dbus_interface=INTERFACE_NAME, signature='s')
+    def ReportFailure(self, message):
+        '''Signals that the backing tool reported failure.'''
+
+    @dbus.service.signal(dbus_interface=INTERFACE_NAME, signature='ss')
+    def RequestAction(self, title, details):
+        '''Signals that the backing tool requests some action.'''
+
+    def _on_run_external_tool(self):
+        '''Executes the external tool.'''
+    def _on_next_chunk(self, chunk):
+        '''Processes the next chunk of output from the external tool.'''
+    def _on_next_line(self, line):
+        '''Processes the next line of output from the external tool.'''
+    def _on_hangup(self):
+        '''Handles termination of the external tool.'''
+
+    # pylint: disable-msg=W0212
+    _pid = property(lambda self: self.__pid)
+
+class IrRecordDriver(ExternalToolDriver):
+    '''D-BUS service that runs irrecord.'''
+
+    # pylint: disable-msg=C0103
+
+    # Following strings are some known error messages of irrecord 0.5,
+    # as shipped with Ubuntu 7.10 on 2008-02-13:
+
+    _errors = {
+        'could not init hardware': _('Could not initialize hardware.'),
+        'gap not found, can\'t continue': _('No key presses recognized. Gap not found.'),
+        'no data for 10 secs, aborting': _('No key presses recognized. Aborting.'),
+    }
+
+    # Following strings indicate state changes in irrecord 0.5,
+    # as shipped with Ubuntu 7.10 on 2008-02-13:
+
+    _token_intro_text       = 'This program will record the signals'
+    _token_hold_one_button  = 'Hold down an arbitrary button.'
+    _token_random_buttons   = 'Now start pressing buttons on your remote control.'
+    _token_next_key         = 'Please enter the name for the next button'
+    _token_wait_toggle_mask = 'If you can\'t see any dots appear'
+    _token_finished         = 'Successfully written config file.'
+
+    # Last instance index for automatic object path creation:
+    __last_instance = 0
+
+    # pylint: disable-msg=R0913
+    def __init__(self, connection, driver, device,
+                 filename='lircd.conf.learning', path=None):
+        assert not os.path.isabs(filename)
+
+        if not path:
+            IrRecordDriver.__last_instance += 1
+            path = '/IrRecordDriver%d' % self.__last_instance
+
+        self._workdir = tempfile.mkdtemp(prefix='gnome-lirc-properties-')
+        self._cmdargs = [config.LIRC_IRRECORD, '--driver=%s' % driver]
+        self._filename = filename
+
+        if device:
+            self._cmdargs.append('--device=%s' % device)
+        if filename:
+            self._cmdargs.append(filename)
+
+        super(IrRecordDriver, self).__init__(connection, path)
+
+    def _on_run_external_tool(self):
+        '''Enters to the working directory and executes irrecord.'''
+
+        os.chdir(self._workdir)
+        self._prepare_workdir()
+
+        args, env = self._cmdargs, {'LC_ALL': 'C'}
+        print 'running %s in %s...' % (args[0], self._workdir)
+        print 'arguments: %r' % args[1:]
+
+        os.execve(args[0], filter(None, args), env)
+
+    def _prepare_workdir(self):
+        '''Virtual method for preparation of the working directory.'''
+
+    def _cleanup_workdir(self):
+        '''Virtual method for cleaning up the working directory.'''
+
+        print 'cleaning up %s...' % self._workdir
+
+        for name in os.listdir(self._workdir):
+            os.unlink(os.path.join(self._workdir, name))
+
+    def _on_hangup(self):
+        '''Shutdown the driver, when irrecord terminates unexpectedly.'''
+
+        if list(self.locations):
+            self.ReportFailure(_('Custom remote control configuration aborted unexpectedly.'))
+            self.Release()
+
+    def _find_error_messages(self, line):
+        '''Tries to identify error messages in line and reports them.'''
+
+        for token, message in self._errors.items():
+            if line.find(token) >= 0:
+                self.ReportFailure(message)
+                self.Release()
+                return True
+
+        return False
+
+    def __del__(self):
+        self._cleanup_workdir()
+        os.rmdir(self._workdir)
+
+class DetectParametersDriver(IrRecordDriver):
+    '''D-BUS service that runs irrecord for detecting basic remote properties.'''
+
+    def __init__(self, connection, driver, device):
+        super(DetectParametersDriver, self).__init__(connection, driver, device)
+        self.__report_progress = False
+
+    def _prepare_workdir(self):
+        '''Removes the configuration file from working directory when needed.'''
+
+        if os.path.exists(self._filename):
+            os.unlink(self._filename)
+
+    def _on_next_line(self, line):
+        '''Processes the next line of irrecord output.'''
+
+        # pylint: disable-msg=R0911
+
+        print '%d:%s' % (self._pid, line)
+
+        # Identify known error messages:
+
+        if self._find_error_messages(line):
+            return
+
+        # Try to catch state changes:
+
+        if line.startswith(self._token_intro_text):
+            self._send_response()
+            return
+
+        if line.startswith(self._token_hold_one_button):
+            self.RequestAction(_('Hold down any remote control button.'), '')
+            return
+
+        if line.startswith(self._token_random_buttons):
+            # TODO: The talk of "steps" here does not make much sense
+            # in terms of a GTK+ progress bar.
+
+            self.RequestAction(
+                _('Press random buttons on your remote control.'),
+                _('When you press the Start button, it is very important '
+                  'that you press many different buttons and hold them down ' +
+                  'for approximately one second. Each button should move the ' +
+                  'progress bar by at least one step, but in no case by more ' +
+                  'than ten steps.'))
+
+            return
+
+        if line.startswith(self._token_next_key):
+            self._send_response()
+            return
+
+        if line.startswith(self._token_wait_toggle_mask):
+            self.RequestAction(
+                _('Press a button repeatedly as fast as possible.'),
+                _('Make sure you keep pressing the <b>same</b> button and that you ' +
+                  '<b>do not hold</b> the button down.\nWait a little between button ' +
+                  'presses if you cannot see any progress.'))
+
+            return
+
+        if line.startswith(self._token_finished):
+            filename = os.path.join(self._workdir, self._filename)
+            configuration = open(filename).read()
+            self.ReportSuccess(configuration)
+            self.Release()
+            return
+
+    def _on_next_chunk(self, chunk):
+        '''
+        Identifies progress indications in the next chunk of irrecord output,
+        and reports them.
+        '''
+
+        if '.' == chunk:
+            self.ReportProgress()
+
+class LearnKeyCodeDriver(IrRecordDriver):
+    '''D-BUS service that runs irrecord for detecting key-codes.'''
+
+    # pylint: disable-msg=R0913
+    def __init__(self, connection, driver, device, configuration, keys):
+        super(LearnKeyCodeDriver, self).__init__(connection, driver, device)
+        self.__keys, self.__configuration = keys, configuration
+
+    def _prepare_workdir(self):
+        '''Writes the supplied configuration file into the  working directory.'''
+
+        open(self._filename, 'w').write(self.__configuration)
+
+    def _on_next_line(self, line):
+        '''Processes the next line of irrecord output.'''
+
+        print '%d:%s' % (self._pid, line)
+
+        # Identify known error messages:
+
+        if self._find_error_messages(line):
+            return
+
+        # Try to catch state changes:
+
+        if line.startswith(self._token_intro_text):
+            self._send_response()
+            return
+
+        if line.startswith(self._token_next_key):
+            if not self.__keys:
+                self._send_response()
+                return
+
+            self._send_response('%s\n' % self.__keys.pop(0))
+            return
+
+        if line.startswith(self._token_finished):
+            configuration = self._find_configuration()
+
+            if configuration:
+                self.ReportSuccess(configuration)
+
+            else:
+                self.ReportFailure(_('Cannot find recorded key codes'))
+
+            self.Release()
+            return
+
+    def _find_configuration(self):
+        '''Finds the configuration file generated by irrecord.'''
+
+        for suffix in '.new', '.conf', '':
+            filename = os.path.join(self._workdir, self._filename + suffix)
+
+            if os.path.isfile(filename):
+                return open(filename).read()
+
+        return None
+
+    def _spawn_external_tool(self):
+        '''Runs irrecord with --resume switch when supported.'''
+        irrecord = os.popen('%s --help' % config.LIRC_IRRECORD)
+
+        if -1 == irrecord.read().find('--resume'):
+            logging.warning('irrecord doesn\'t have --resume switch')
+
+            self.ReportFailure(_(
+                'The installed lirc does not support key code learning ' +
+                'because its irrecord command does not have the --resume ' +
+                'option. Please file a bug against your Linux distribution.'))
+
+            return 0, -1
+
+        self._cmdargs.insert(1, '--resume')
+        return super(LearnKeyCodeDriver, self)._spawn_external_tool()
+
+class BackendService(PolicyKitService):
+    '''A D-Bus service that PolicyKit controls access to.'''
+
+    # pylint: disable-msg=C0103,E0602
+
+    INTERFACE_NAME = 'org.gnome.LircProperties.Mechanism'
+    SERVICE_NAME   = 'org.gnome.LircProperties.Mechanism'
+    IDLE_TIMEOUT   =  30
+
+    # These are extra fields set by our GUI:
+
+    __re_receiver_directive = re.compile(r'^\s*RECEIVER_(VENDOR|MODEL)=')
+
+    # These are used by the Debian/Ubuntu packages, as of 2008-02-12.
+    # The "REMOTE_" prefix is made optional, since it only was introduced
+    # with lirc 0.8.3~pre1-0ubuntu4 of Hardy Heron.
+
+    __re_remote_directive = re.compile(r'^\s*(?:REMOTE_)?(DRIVER|DEVICE|MODULES|' +
+                                       r'LIRCD_ARGS|LIRCD_CONF|VENDOR|MODEL)=')
+    __re_start_lircd      = re.compile(r'^\s*START_LIRCD=')
+
+    def __init__(self, connection=None, path='/'):
+        if connection is None:
+            connection = get_service_bus()
+
+        super(BackendService, self).__init__(connection, path)
+
+        self.__name = dbus.service.BusName(self.SERVICE_NAME, connection)
+        self.__loop = gobject.MainLoop()
+        self.__timeout = 0
+
+        connection.add_message_filter(self.__message_filter)
+
+    def __message_filter(self, connection, message):
+        '''
+        D-BUS message filter that keeps the service alive,
+        as long as it receives message.
+        '''
+
+        if self.__timeout:
+            self.__start_idle_timeout()
+
+        return HANDLER_RESULT_NOT_YET_HANDLED
+
+    def __start_idle_timeout(self):
+        '''Restarts the timeout for terminating the service when idle.'''
+
+        if self.__timeout:
+            gobject.source_remove(self.__timeout)
+
+        self.__timeout = gobject.timeout_add(self.IDLE_TIMEOUT * 1000,
+                                             self.__timeout_cb)
+
+    def __timeout_cb(self):
+        '''Timeout callback that terminates the service when idle.'''
+
+        # Keep service alive, as long as additional objects are exported:
+        if self.connection.list_exported_child_objects('/'):
+            return True
+
+        print 'Terminating %s due to inactivity.' % self.SERVICE_NAME
+        self.__loop.quit()
+
+        return False
+
+    def run(self):
+        '''Creates a GLib main loop for keeping the service alive.'''
+
+        print 'Running %s.' % self.SERVICE_NAME
+        print 'Terminating it after %d seconds of inactivity.' % self.IDLE_TIMEOUT
+
+        self.__start_idle_timeout()
+        self.__loop.run()
+
+    def _write_hardware_configuration(self,
+                                      remote_values=dict(),
+                                      receiver_values=dict(),
+                                      start_lircd=None):
+        '''Updates lirc's hardware.conf file on Debian/Ubuntu.'''
+
+        if start_lircd is not None:
+            start_lircd = str(bool(start_lircd)).lower()
+
+        oldfile = config.LIRC_HARDWARE_CONF
+        newfile = '%s.tmp' % oldfile
+
+        if not os.path.isfile(oldfile):
+            raise UnsupportedException('Cannot find %s script' % oldfile)
+
+        logging.info('Updating %s...', oldfile)
+        logging.info('- receiver_values: %r', receiver_values)
+        logging.info('- remote_values: %r', remote_values)
+
+        output = file(newfile, 'w')
+        for line in file(oldfile, 'r'):
+
+            # Identify directives starting with REMOTE_ and replacing their values with ours.
+            # Remove entry from the dict on match, so we know what was written.
+            match = self.__re_remote_directive.match(line)
+            value = match and remote_values.pop(match.group(1), None)
+
+            if value is not None:
+                logging.info('- writing %s"%s"', match.group(0), value)
+                print >> output, ('%s"%s"' % (match.group(0), value))
+                continue
+
+            # Identify directives starting with RECEIVER_ and replacing their values with ours.
+            # Remove entry from the dict on match, so we know what was written.
+            match = self.__re_receiver_directive.match(line)
+            value = match and receiver_values.pop(match.group(1), None)
+
+            if value is not None:
+                logging.info('- writing %s"%s"', match.group(0), value)
+                print >> output, ('%s"%s"' % (match.group(0), value))
+                continue
+
+            # Deal with the START_LIRCD line:
+
+            match = self.__re_start_lircd.match(line)
+
+            if match:
+                # pychecker says "Using a conditional statement with a constant value (true)",
+                # which is ridicilous, considering Python 2.4 doesn't have conditional statements
+                # yet (PEP 308, aka. 'true_value if condition else false_value') and the expression
+                # below ('condition and true_value or false_value') is the recommended aquivalent.
+                value = (start_lircd is None) and 'true' or start_lircd
+                start_lircd = None
+
+                print >> output, (match.group(0) + value)
+                continue
+
+            output.write(line)
+
+        # Write out any values that were not already in the file,
+        # and therefore just replaced:
+
+        if remote_values:
+            print >> output, '\n# Remote settings required by gnome-lirc-properties'
+        for key, value in remote_values.items():
+            print >> output, ('REMOTE_%s="%s"' % (key, value))
+
+        if receiver_values:
+            print >> output, '\n# Receiver settings required by gnome-lirc-properties'
+        for key, value in receiver_values.items():
+            print >> output, ('RECEIVER_%s="%s"' % (key, value))
+
+        if start_lircd is not None:
+            print >> output, '\n# Daemon settings required by gnome-lirc-properties'
+            print >> output, ('START_LIRCD=%s' % start_lircd)
+
+        # Replace old file with new contents:
+
+        os.unlink(oldfile)
+        os.rename(newfile, oldfile)
+
+    # pylint: disable-msg=R0913
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='sssss', out_signature='',
+                         sender_keyword='sender')
+    def WriteReceiverConfiguration(self, vendor, product,
+                                   driver, device, modules,
+                                   sender=None):
+        '''
+        Update the /etc/lirc/hardware.conf file,
+        so that lircd is started as specified.
+        '''
+
+        self._check_permission(sender)
+
+        remote_values = {
+            'DRIVER': driver,
+            'DEVICE': device,
+            'MODULES': modules,
+            'LIRCD_ARGS': '',
+            'LIRCD_CONF': '',
+        }
+
+        receiver_values = {
+            'VENDOR': vendor,
+            'MODEL': product,
+        }
+
+        self._write_hardware_configuration(remote_values, receiver_values)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='s', out_signature='',
+                         sender_keyword='sender')
+    def WriteRemoteConfiguration(self, contents, sender=None):
+        '''
+        Write the contents to the system lircd.conf file.
+        PolicyKit will not allow this function to be called without sudo/root
+        access, and will ask the user to authenticate if necessary, when
+        the application calls PolicyKit's ObtainAuthentication().
+        '''
+
+        self._check_permission(sender)
+
+        if not contents:
+            raise UsageError('Bad IR remote configuration file')
+
+        # Parse contents:
+
+        hwdb = lirc.RemotesDatabase()
+        hwdb.read(StringIO(contents))
+        remote = len(hwdb) and hwdb[0]
+
+        # Update hardware.conf with choosen remote:
+
+        if remote:
+            values = {
+                'VENDOR': remote.vendor or _('Unknown'),
+                'MODEL': remote.product or remote.name
+            }
+
+            self._write_hardware_configuration(remote_values=values)
+
+        # Write remote configuration:
+        filename, include_needed = lirc.find_remote_config()
+
+        if include_needed:
+            self.__write_include_statement(filename)
+
+        print contents
+
+        print 'Updating %s...' % filename
+        file(filename, 'w').write(contents)
+
+    @staticmethod
+    def __write_include_statement(redirect):
+        '''Write include statement to central lircd.conf file.'''
+
+        # read central lirc configuration file file:
+        try:
+            contents = open(config.LIRC_DAEMON_CONF).read()
+
+        except IOError:
+            contents = ''
+
+        # drop entire configuration file, if it still contains embedded
+        # configuration files - for instance from Gutsy:
+        pattern = r'^\s*begin\s+remote\s*$'
+        pattern = re.compile(pattern, re.MULTILINE)
+        match   = pattern.search(contents)
+
+        if match:
+            contents = ''
+
+        # find existing include statement:
+        include_statement = 'include %s\n' % redirect
+        pattern = r'^\s*(#.*)?include\s+%s\s*$' % re.escape(redirect)
+        pattern = re.compile(pattern, re.MULTILINE)
+        match   = pattern.search(contents)
+
+        if match is None:
+            # no statement found, create entirely new file:
+
+            if not contents:
+                contents  = '# This configuration has been automatically generated\n'
+                contents += '# by the GNOME LIRC Properties control panel.\n'
+                contents += '#\n'
+                contents += '# Feel free to add any custom remotes to the configuration\n'
+                contents += '# via additional include directives or below the existing\n'
+                contents += '# Ubuntu include directives from your selected remote and/or\n'
+                contents += '# transmitter.\n'
+                contents += '#\n'
+
+            contents += '\n'
+            contents += '# Configuration selected with GNOME LIRC Properties\n'
+            contents += include_statement
+
+        elif match.group(1):
+            head = contents[:match.start()]
+            tail = contents[match.end():]
+            contents = (head + include_statement + tail)
+
+        else:
+            contents = None
+
+        if contents:
+            open(config.LIRC_DAEMON_CONF, 'w').write(contents)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='', out_signature='',
+                         sender_keyword='sender')
+    def ManageLircDaemon(self, action, sender=None):
+        '''Starts the LIRC daemon.'''
+
+        self._check_permission(sender)
+
+        print 'Managing lircd: %s...' % action
+
+        if 'enable' == action:
+            self._write_hardware_configuration(start_lircd=True)
+
+        elif 'disable' == action:
+            self._write_hardware_configuration(start_lircd=False)
+
+        else:
+            args = '/etc/init.d/lirc', action
+            os.spawnv(os.P_WAIT, args[0], args)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='ss', out_signature='o',
+                         sender_keyword='sender')
+    def DetectParameters(self, driver, device, sender=None):
+        '''Detects parameters of the IR remote by running irrecord.'''
+
+        self._check_permission(sender)
+
+        return DetectParametersDriver(self.connection, driver, device)
+
+    # pylint: disable-msg=R0913
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='sssas', out_signature='o',
+                         sender_keyword='sender')
+    def LearnKeyCode(self, driver, device, configuration, keys, sender=None):
+        '''Learn the scan code of some IR remote key by running irrecord.'''
+
+        self._check_permission(sender)
+
+        return LearnKeyCodeDriver(self.connection,
+                                  driver, device,
+                                  configuration,
+                                  keys)
+
+    @dbus.service.method(dbus_interface=INTERFACE_NAME,
+                         in_signature='s', out_signature='',
+                         sender_keyword='sender')
+    def InstallRemoteDatabase(self, filename, sender=None):
+        '''Update the customized receiver database.'''
+
+        # Copy the tarball with updates into our data folder
+        # to allow atomic replacement of the old tarball:
+        tarball = os.path.join(config.PACKAGE_DIR, 'remotes-update.tar.gz')
+
+        if not os.path.isdir(config.PACKAGE_DIR):
+            os.makedirs(config.PACKAGE_DIR)
+
+        shutil.copyfile(filename, tarball)
+
+        # Now practice the atomic replacement:
+        os.rename(tarball, config.LIRC_REMOTES_TARBALL)
+
+        # Finally adjust timestamps of the archive:
+        timestamps = os.path.getatime(filename), os.path.getmtime(filename)
+        os.utime(config.LIRC_REMOTES_TARBALL, timestamps)
+
+def get_service_bus():
+    '''Retrieves a reference to the D-BUS system bus.'''
+
+    return dbus.SystemBus()
+
+def get_service(bus=None):
+    '''Retrieves a reference to the D-BUS driven configuration service.'''
+
+    if not bus:
+        bus = get_service_bus()
+
+    service = bus.get_object(BackendService.SERVICE_NAME, '/')
+    service = dbus.Interface(service, BackendService.INTERFACE_NAME)
+
+    return service
+
+if __name__ == '__main__':
+    # Support full tracing when --debug switch is passed:
+
+    from sys import argv
+
+    if '--debug' in argv or '-d' in argv:
+        logging.getLogger().setLevel(logging.NOTSET)
+
+    # Integrate DBus with GLib main loops.
+
+    from dbus.mainloop.glib import DBusGMainLoop
+    DBusGMainLoop(set_as_default=True)
+
+    # Run the service.
+
+    BackendService().run()
+

Added: trunk/gnome_lirc_properties/config.py.in
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/config.py.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,30 @@
+"""
+Configuration information.
+Generated by config.status from config.py.in.
+"""
+
+from gnome_lirc_properties.lsb import ReleaseInfo
+from os                        import path
+
+LSB_RELEASE           = ReleaseInfo()
+
+ENABLE_POLICY_KIT     = @ENABLE_POLICY_KIT@
+POLICY_KIT_ACTION     = 'org.gnome.lirc-properties.mechanism.configure'
+
+PACKAGE_DIR           = path.join('@prefix@', 'share', '@PACKAGE@')
+
+LIRC_CONFDIR          = '@with_lirc_confdir@'
+
+LIRC_HARDWARE_CONF    = path.join(LIRC_CONFDIR, 'hardware.conf')
+LIRC_REMOTE_CONF      = path.join(LIRC_CONFDIR, 'lircd.conf.gnome')
+LIRC_DAEMON_CONF      = path.join(LIRC_CONFDIR, 'lircd.conf')
+
+LIRC_REMOTES_DATABASE = '@with_remotes_database@'
+LIRC_REMOTES_TARBALL  = path.join(PACKAGE_DIR, 'remotes.tar.gz')
+LIRC_IRRECORD         = '@LIRC_IRRECORD@'
+
+URI_UPLOADS           = '@with_upload_uri@'
+URI_UPDATES           = '@with_download_uri@'
+
+# remove temporary objects from namespace
+del path, ReleaseInfo

Added: trunk/gnome_lirc_properties/hardware.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/hardware.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,484 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Hardware Abstraction Layer (HAL) related classes.
+'''
+
+import dbus
+import gobject
+import gtk.gdk
+import logging
+import os, os.path
+
+from ConfigParser          import SafeConfigParser
+from gettext               import gettext as _
+from gnome_lirc_properties import lirc
+
+HAL_SERVICE = 'org.freedesktop.Hal'
+HAL_MANAGER_PATH = '/org/freedesktop/Hal/Manager'
+HAL_MANAGER_IFACE = 'org.freedesktop.Hal.Manager'
+
+class HalDevice(object):
+    '''A device as announced by HAL.'''
+
+    def __init__(self, bus, hal, udi):
+        proxy_object = bus.get_object ('org.freedesktop.Hal', udi)
+        self.__obj = dbus.Interface (proxy_object, 'org.freedesktop.Hal.Device')
+        self.__hal = hal
+        self.__bus = bus
+        self.__udi = udi
+
+    def __getitem__(self, key):
+        try:
+            return self.__obj.GetProperty(key)
+
+        except dbus.exceptions.DBusException, ex:
+            if ('org.freedesktop.Hal.NoSuchProperty' == ex.get_dbus_name()):
+                raise KeyError, key
+
+            raise
+
+    def get(self, key, default = None):
+        '''
+        Returns the value of the property described by key,
+        or default if the property doesn't exist.
+        '''
+        try:
+            return self.__obj.GetProperty(key)
+
+        except dbus.exceptions.DBusException, ex:
+            if ('org.freedesktop.Hal.NoSuchProperty' == ex.get_dbus_name()):
+                return default
+
+            raise
+
+    def lookup_parent(self):
+        '''Find the parent device of this device.'''
+        return HalDevice(self.__bus, self.__hal, self['info.parent'])
+
+    def find_children(self):
+        '''Lists all child devices of this device.'''
+        children = self.__hal.FindDeviceStringMatch('info.parent', self.__udi)
+        return [HalDevice(self.__bus, self.__hal, udi) for udi in children]
+
+    def check_kernel_module(self, kernel_module):
+        '''Checks if the driver uses the specified kernel module.'''
+        for device in self.find_children():
+            if device.get('info.linux.driver') == kernel_module:
+                return True
+
+        return False
+
+    def find_device_by_class(self, device_class):
+        '''Find the LIRC device node associated with this device.'''
+        class_root = os.path.join(os.sep, 'sys', 'class', device_class)
+        sysfs_path = self['linux.sysfs_path']
+
+        # the class root does not exist when no such driver is loaded:
+        if not os.path.isdir(class_root):
+            return None
+
+        for device in os.listdir(class_root):
+            try:
+                # resolve the device link found in the sysfs folder:
+                root = os.path.join(class_root, device)
+                link = os.path.join(root, 'device')
+                link = os.path.join(root, os.readlink(link))
+
+                # compare the resolved link and its parent with the device path
+                # stored in the 'linux.sysfs_path' property:
+                while link:
+                    if not os.path.samefile(sysfs_path, link):
+                        link = os.path.dirname(link)
+                        continue
+
+                    for filename in (
+                        os.path.join(os.sep, 'dev', device_class, device),
+                        os.path.join(os.sep, 'dev', device),
+                    ):
+                        if os.path.exists(filename):
+                            return filename
+
+                    break
+
+            except (IOError, OSError), ex:
+                logging.warning(ex)
+
+        return None
+
+    def find_input_device(self):
+        '''Find the Linux Input System device node associated with this device.'''
+        for device in self.find_children():
+            if device.get('info.category') == 'input':
+                return device['input.device']
+
+        return None
+
+    def find_device_node(self, kernel_module, lirc_driver):
+        '''Finds the device node associated with this device.'''
+
+        if 'usbhid' == kernel_module:
+            return self.find_device_by_class('usb')
+        if 'default' == lirc_driver:
+            return self.find_device_by_class('lirc')
+        if lirc_driver in ('devinput', 'dev/input'):
+            return self.find_input_device()
+
+        return None
+
+    def __str__(self):
+        return self.__udi
+    def __repr__(self):
+        return '<HalDevice: %s>' % self.__udi
+
+    # pylint: disable-msg=W0212
+    udi = property(lambda self: self.__udi)
+
+class HardwareDatabase(SafeConfigParser):
+    '''Information about supported hardware.'''
+    def __init__(self, filename):
+        SafeConfigParser.__init__(self)
+        self.read(filename)
+
+class HardwareManager(gobject.GObject):
+    '''This object provides hardware detection.'''
+
+    __gsignals__ = {
+        'search-progress': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            (gobject.TYPE_FLOAT, gobject.TYPE_STRING)),
+        'search-finished': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            tuple()),
+        'receiver-found': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            (gobject.TYPE_PYOBJECT, gobject.TYPE_STRING,
+             gobject.TYPE_STRING)),
+
+        'receiver-added': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            (gobject.TYPE_PYOBJECT, )),
+        'receiver-removed': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            (gobject.TYPE_PYOBJECT, ))
+    }
+
+    def __init__(self, hardware_db):
+        # pylint: disable-msg=E1002
+
+        assert(isinstance(hardware_db, HardwareDatabase))
+
+        super(HardwareManager, self).__init__()
+
+        self.__devinput_receivers = dict()
+        self.__search_canceled = False
+        self.__device_count = 0.0
+        self.__progress = 0.0
+
+        self.__bus = dbus.SystemBus()
+        self.__hal = self.__bus.get_object(HAL_SERVICE, HAL_MANAGER_PATH)
+        self.__hal = dbus.Interface(self.__hal, HAL_MANAGER_IFACE)
+
+        self.__hal.connect_to_signal('DeviceAdded', self._on_device_added)
+        self.__hal.connect_to_signal('DeviceRemoved', self._on_device_removed)
+
+        for udi in self.__hal.FindDeviceByCapability('input.keyboard'):
+            self._on_device_added(udi)
+
+        # reading hardware database, and reording as
+        # dictionary with (product-id, vendor-id) keys
+        self.__receivers = dict()
+
+        for section in hardware_db.sections():
+            vendor, product = [s.strip() for s in section.split(':', 1)]
+            properties = dict(hardware_db.items(section))
+
+            receiver = lirc.Receiver(vendor, product, **properties)
+
+            if receiver.vendor_id:
+                key = (receiver.vendor_id, receiver.product_id)
+
+            elif receiver.kernel_module:
+                key = receiver.kernel_module
+
+            else:
+                key = receiver.lirc_driver
+
+            self.__receivers[key] = receiver
+
+    def __get_devinput_receivers(self):
+        '''List all currently known Input Device Layer receiver.'''
+        return self.__devinput_receivers
+
+    def _on_device_added(self, udi, sender=None):
+        '''Handle addition of hot-plugable devices.'''
+
+        device = self.lookup_device(udi)
+
+        if 'input.keyboard' in device.get('info.capabilities', []):
+            product_name = str(device['info.product'])
+            device_node = str(device['input.device'])
+
+            properties = {
+                'compatible-remotes': 'linux-input-layer',
+                'lirc-driver': 'devinput',
+                'device': device_node,
+                'udi': udi,
+            }
+
+            receiver = lirc.Receiver(_('Linux Input Device'),
+                                     product_name, **properties)
+
+            self.__devinput_receivers[udi] = receiver
+            self.emit('receiver-added', receiver)
+
+    def _on_device_removed(self, udi, sender=None):
+        '''Handle removal of hot-plugable devices.'''
+
+        receiver = self.__devinput_receivers.pop(udi, None)
+
+        if receiver is not None:
+            self.emit('receiver-removed', receiver)
+
+    def resolve_device_nodes(self, nodes):
+        '''Resolve the requested device nodes.'''
+        device_nodes = list()
+
+        for name in nodes:
+            # Use HAL manager to find device nodes,
+            # when name starts with "hal-capability:"
+            if name.startswith('hal-capability:'):
+                capability = name.split(':')[1]
+                device_property = '%s.device' % capability.split('.')[0]
+
+                # Find devices with requested capability:
+
+                devices = [
+                    self.lookup_device(udi)
+                    for udi in self.__hal.FindDeviceByCapability(capability)]
+
+                # Extract device node and product name for matching devices:
+
+                devices = [(
+                    device['info.product'],
+                    device.get(device_property))
+                    for device in devices]
+
+                # Throw away devices without device node:
+
+                device_nodes += [
+                    (str(name), str(device))
+                    for name, device in devices
+                    if device is not None]
+
+            elif name.startswith('numeric:'):
+                device_nodes.append(name)
+
+            # Otherwise check if name is an existing device node.
+            elif os.path.isabs(name) and os.path.exists(name):
+                device_nodes.append(name)
+
+        # Ensure that each entry exists only once, and sort them.
+        device_nodes = list(set(device_nodes))
+        device_nodes.sort()
+
+        return device_nodes
+
+    @staticmethod
+    def parse_numeric_device_node(device_node):
+        '''
+        Parses a numeric device node as used for describing for instance UDP
+        receivers. Returns the tuple label, default-value, minimum-value,
+        maximum-value.
+        '''
+
+        parts  = device_node.split(':', 4)
+        limits = map(int, parts[1:4])
+        label  = parts[4]
+
+        return [label] + limits
+
+    def __report_search_progress(self, product_name):
+        '''Reports progress when searching supported devices.'''
+
+        self._search_progress(self.__progress / self.__device_count,
+                              product_name)
+
+        logging.info('%d/%d: %s', self.__progress,
+                     self.__device_count, product_name)
+
+        self.__progress += 1.0
+
+    def __find_usb_receivers(self, usb_devices):
+        '''Find IR receivers connected via USB bus.'''
+
+        for udi in usb_devices:
+            # provide cancelation point:
+            if self.__search_canceled:
+                break
+
+            # process GTK+ events:
+            while gtk.events_pending():
+                if gtk.main_iteration():
+                    return
+
+            # lookup the current device:
+            device = self.lookup_device(udi)
+
+            # lookup the USB devices in our hardware database:
+            vendor_id = device.get('usb_device.vendor_id')
+            product_id = device.get('usb_device.product_id')
+
+            # skip USB devices that do not get sufficient power:
+            if vendor_id is None or product_id is None:
+                continue
+
+            # report search progress:
+            self.__report_search_progress('%s %s' % (device['info.vendor'],
+                                                     device['info.product']))
+
+            # Skip devices without vendor id. Linux for instance
+            # doesn't assign a vendor id to USB host controllers.
+            if not vendor_id:
+                continue
+
+            # The Streamzap really has a product ID of 0000,
+            # so don't skip like this on empty product ids:
+            #
+            #   if not vendor_id or not product_id:
+            #       continue
+            #
+
+            # lookup receiver description:
+            receiver_key = (vendor_id, product_id)
+            receiver = self.__receivers.get(receiver_key)
+
+            # skip unknown hardware:
+            if not receiver:
+                continue
+
+            # skip devices, where the associated kernel module
+            # doesn't match the expected kernel module:
+            if (receiver.kernel_module and
+                not device.check_kernel_module(receiver.kernel_module)):
+                continue
+
+            # find the LIRC device node, and signal search result:
+            device_node = device.find_device_node(receiver.kernel_module,
+                                                  receiver.lirc_driver)
+
+            self._receiver_found(receiver, device.udi, device_node)
+
+    def __find_input_layer_receivers(self, input_devices):
+        '''Find IR receivers implementing the Linux Input Layer interface.'''
+
+        for device in input_devices:
+            # provide cancelation point:
+            if self.__search_canceled:
+                break
+
+            # process GTK+ events:
+            while gtk.events_pending():
+                if gtk.main_iteration():
+                    return
+
+            # lookup the current device:
+            device = self.lookup_device(device)
+            product_name = device.get('info.product')
+            device_node = device.get('input.device')
+
+            # report search progress:
+            self.__report_search_progress(product_name)
+
+            # skip input device that do not provide sufficient information:
+            if product_name is None or device_node is None:
+                continue
+
+            # skip input devices that have the word "keyboard" in their product name:
+            if product_name.lower().find('keyboard') >= 0:
+                continue
+
+            # report findings:
+            receiver = self.devinput_receivers[device.udi]
+            self._receiver_found(receiver, device.udi, device_node)
+
+    def find_instance(self, receiver):
+        '''Finds the device node associated with this receiver.'''
+
+        for udi in self.__hal.FindDeviceStringMatch('info.subsystem', 'usb_device'):
+            device = self.lookup_device(udi)
+
+            if (device['usb_device.vendor_id'] == receiver.vendor_id and
+                device['usb_device.product_id'] == receiver.product_id):
+                return device.find_device_node(receiver.kernel_module,
+                                               receiver.lirc_driver)
+
+        return None
+
+    def lookup_device(self, udi):
+        '''Looks up the HAL device for that UDI.'''
+        return HalDevice(self.__bus, self.__hal, udi)
+
+    def search_receivers(self):
+        '''
+        Search for supported IR receivers. This search actually happens
+        in a separate thread and can be aborted with the cancel() method.
+        Connect to the signals of this object, to monitor search progress.
+
+        TODO: Currently the search happens in the main thread, as DBus' Python
+        bindings still cause random dead-locks when used with threads. Despite
+        claiming being thread-safe.
+        '''
+
+        # retreive list of USB devices from HAL
+        usb_devices = self.__hal.FindDeviceStringMatch('info.subsystem', 'usb_device')
+        input_devices = self.__hal.FindDeviceByCapability('input.keyboard')
+
+        self.__search_canceled = False
+        self.__device_count = float(len(usb_devices) + len(input_devices))
+        self.__progress = 0.0
+
+        self.__find_usb_receivers(usb_devices)
+        self.__find_input_layer_receivers(input_devices)
+
+        self._search_finished()
+
+    def _search_progress(self, progress, message, *args):
+        '''Report search progress.'''
+
+        # pylint: disable-msg=E1101
+        self.emit('search-progress', progress, message % args)
+
+    def _receiver_found(self, receiver, udi, device_node):
+        '''Signal that a receiver was found.'''
+
+        # pylint: disable-msg=E1101
+        self.emit('receiver-found', receiver, udi, device_node)
+
+    def _search_finished(self):
+        '''Signal that search has finished.'''
+
+        # pylint: disable-msg=E1101
+        self.emit('search-finished')
+
+    def cancel(self):
+        '''Abort current search process.'''
+
+        self.__search_canceled = True
+
+    devinput_receivers = property(__get_devinput_receivers)

Added: trunk/gnome_lirc_properties/lirc.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/lirc.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,1023 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+LIRC specific classes.
+'''
+
+import errno, gobject, logging, os, re, tarfile
+
+from datetime              import datetime
+from gettext               import gettext as _
+from gnome_lirc_properties import config
+from locale                import LC_TIME, setlocale
+from StringIO              import StringIO
+from socket                import AF_UNIX, socket, error as SocketError
+
+class ParseError(Exception):
+    '''
+    Execption describing a parse error.
+    '''
+
+    def __init__(self, filename, lineno, message = _('Malformed configuration file')):
+        message = '%s:%d: %s' % (filename, lineno, message)
+        super(ParseError, self).__init__(message)
+
+class Receiver(object):
+    '''
+    Description of an IR receiver.
+    '''
+
+    def __init__(self, vendor, product, **properties):
+        self.__vendor, self.__product = vendor, product
+        self.__vendor_id = int(properties.get('vendor-id', '0'), 0)
+        self.__product_id = int(properties.get('product-id', '0'), 0)
+        self.__properties = dict(properties)
+
+        self.__compatible_remotes = properties.get('compatible-remotes')
+
+        if self.__compatible_remotes:
+            self.__compatible_remotes = [
+                name.strip() for name in
+                self.__compatible_remotes.split(',')]
+
+    def find_supplied_remote(self, remotes_db):
+        '''
+        Look up compatible remotes in the remotes db,
+        and return the first one found as "supplied remote".
+        '''
+
+        remotes = self.__compatible_remotes or []
+        remotes = map(remotes_db.get, remotes)
+        remotes = filter(None, remotes)
+
+        return remotes and remotes[0]
+
+    def update_configuration(self, mechanism, device):
+        '''
+        Updates configuration files to use this kind of receiver.
+        '''
+
+        mechanism.WriteReceiverConfiguration(self.vendor, self.product,
+                                             self.lirc_driver or 'default',
+                                             device or '', self.kernel_module or '')
+
+    def __str__(self):
+        return '%s: %s' % (self.__vendor, self.__product)
+    def __repr__(self):
+        return '<Receiver: vendor=%s, product=%s>' % (self.__vendor, self.__product)
+
+    def __getitem__(self, key):
+        return self.__properties[key]
+
+    # pylint: disable-msg=W0212
+    vendor = property(lambda self: self.__vendor)
+    vendor_id = property(lambda self: self.__vendor_id)
+
+    product = property(lambda self: self.__product)
+    product_id = property(lambda self: self.__product_id)
+
+    device = property(lambda self: self.__properties.get('device'))
+    device_nodes = property(lambda self: self.__properties.get('device-nodes'))
+    kernel_module = property(lambda self: self.__properties.get('kernel-module'))
+    lirc_driver = property(lambda self: self.__properties.get('lirc-driver', 'default'))
+    compatible_remotes = property(lambda self: self.__compatible_remotes)
+
+class Remote(object):
+    """
+    Description of an IR remote.
+    """
+
+    # pylint: disable-msg=R0913
+
+    def __init__(self, database = None, filename = None,
+                 vendor = None, product = None, contributor = None,
+                 properties = list(), key_codes = dict()):
+
+        self.__properties = dict(properties)
+        self.__property_order = [item[0] for item in properties]
+        self.__key_codes = key_codes
+        self.__filename = filename
+        self.__database = database
+
+        self.__vendor = vendor
+        self.__product = product
+        self.__name = ' '.join(self.__properties.get('name'))
+        self.__contributor = contributor
+
+    def write(self, writer):
+        '''
+        Writes configuration file of this remote to writer.
+        '''
+
+        # Retrieve locale independent timestamp:
+        locale = setlocale(LC_TIME, 'C')
+        now = datetime.now().strftime('%c')
+        setlocale(LC_TIME, locale)
+
+        # Write comment header with meta information:
+        print >> writer, '# LIRC configuration file for %s' % self.name
+        print >> writer, '# Generated by GNOME LIRC properties on %s' % now
+        print >> writer, '# from %s' % self.filename
+        print >> writer, '#'
+
+        if self.contributor:
+            print >> writer, '# contributed by %s' % self.contributor
+            print >> writer, '#'
+
+        if self.vendor:
+            print >> writer, '# brand: %s' % self.vendor
+        if self.product:
+            print >> writer, '# model no. of remote control: %s' % self.product
+        if self.vendor or self.product:
+            print >> writer, '#'
+
+        # Write remote block
+
+        ident = -max([len(key) for key in self.__property_order])
+
+        print >> writer
+        print >> writer, 'begin remote'
+
+        for key in self.__property_order:
+            args = ident, key, ' '.join(self.__properties[key])
+            print >> writer, '  %*s  %s' % args
+
+        # Write key codes
+
+        print >> writer
+        print >> writer, '  begin codes'
+
+        key_codes = list(self.__key_codes.items())
+        key_codes.sort(lambda a, b: cmp(a[0], b[0]))
+
+        ident = -max([len(s) for s in self.__key_codes.keys() + ['']])
+
+        for key, value in key_codes:
+            print >> writer, '    %*s  %s' % (ident, key, value)
+
+        print >> writer, '  end codes'
+        print >> writer, 'end remote'
+
+        return writer
+
+    def __get_configuration(self):
+        '''
+        Retreives the configuration file of this remote as string.
+        '''
+        return self.write(StringIO()).getvalue()
+
+    def __set_properties(self, value):
+        '''
+        Updates the basic properties of this remote.
+        '''
+
+        self.__properties = dict(value)
+        self.__property_order = list(self.__properties.keys())
+        self.__property_order.sort()
+
+    def update_configuration(self, mechanism):
+        '''
+        Updates configuration files to use this remote.
+        '''
+
+        mechanism.WriteRemoteConfiguration(self.configuration)
+
+    def __repr__(self):
+        return '<Remote: %s>' % self.__name
+
+    # pylint: disable-msg=W0212
+    name = property(lambda self: self.__name)
+    product = property(lambda self: self.__product)
+    vendor = property(lambda self: self.__vendor)
+    contributor = property(lambda self: self.__contributor)
+
+    configuration = property(__get_configuration)
+
+    database = property(lambda self: self.__database)
+    filename = property(lambda self: self.__filename)
+    key_codes = property(lambda self: self.__key_codes)
+    properties = property(lambda self: self.__properties, __set_properties)
+
+class RemotesDatabase(object):
+    '''
+    Reads and interprets an IR remote configuration file,
+    such as /etc/lirc/lircd.conf or the ones at /usr/share/lirc/remotes.
+    '''
+
+    def __init__(self):
+        super(RemotesDatabase, self).__init__()
+        self.__remotes = dict()
+
+    def clear(self):
+        '''
+        Removes all database entries.
+        '''
+
+        self.__remotes.clear()
+
+    def load_folder(self, root=config.LIRC_REMOTES_DATABASE):
+        '''
+        Recursively reads all configuration files found in that folder.
+        '''
+
+        if not os.path.isdir(root):
+            logging.warning('Cannot read remote database folder: %s', root)
+            return False
+
+        logging.info('Reading remote database from %s...', root)
+
+        for path, subdirs, files in os.walk(root):
+            # skip folders with meta information
+            subdirs[:] = [name for name in subdirs
+                          if not name in ('CVS', ) and
+                             not name.startswith('.')]
+
+            # extract path component after the root path for current folder:
+            relative_path = path[len(root):]
+
+            while os.path.isabs(relative_path):
+                relative_path = relative_path[len(os.path.sep):]
+
+            # read files in current folder:
+            for name in files:
+                if not name.startswith('.'):
+                    self.read(reader=open(os.path.join(path, name)),
+                              filename=os.path.join(relative_path, name),
+                              database=root)
+
+        return True
+
+    def load_tarball(self, path=config.LIRC_REMOTES_TARBALL):
+        '''
+        Reads remote configurations from tarball.
+        '''
+
+        if not os.path.isfile(path):
+            return False
+
+        if not tarfile.is_tarfile(path):
+            logging.warning('Bad remote control archive: %s', path)
+            return False
+
+        logging.info('Reading remote database from %s...', path)
+        tarball = tarfile.open(path)
+
+        for entry in tarball:
+            if not entry.isfile():
+                continue
+            if os.path.basename(entry.name) == 'README':
+                continue
+
+            self.read(reader=tarball.extractfile(entry.name),
+                      filename=entry.name, database=path,
+                      replace=True)
+
+        return True
+
+    def load(self, filename):
+        '''
+        Reads remote configuration from filename.
+        '''
+
+        self.read(open(filename),
+                  database=os.path.dirname(filename),
+                  filename=os.path.basename(filename))
+
+    def read(self, reader, filename=None, database=None, replace=False):
+        '''
+        Reads remote configuration from reader.
+        '''
+
+        parser = RemotesParser()
+
+        try:
+            parser.read(reader, filename, database)
+
+        except ParseError, ex:
+            logging.error(ex.message)
+            return False
+
+        self.__update_remotes(parser.remotes, replace)
+
+        return True
+
+    def __update_remotes(self, remotes, replace=False):
+        '''
+        Updates the remotes dictionary.
+        '''
+
+        for remote in remotes:
+            previous_remote = self.__remotes.get(remote.name)
+
+            if not previous_remote or (replace and remote.database != previous_remote.database):
+                self.__remotes[remote.name] = remote
+                continue
+
+            logging.warning('%s: Remote %s listed twice in %s and %s.',
+                            remote.database, remote.name, remote.filename,
+                            previous_remote.filename)
+
+    def get(self, name, default=None):
+        '''Looks up the remote matching name.'''
+
+        return self.__remotes.get(name, default)
+
+    def find(self, vendor, product, default=None):
+        '''Tries to find the specified remote control.'''
+
+        for remote in self:
+            if (remote.vendor == vendor and
+                remote.product == product):
+                return remote
+
+        return default
+
+    def __iter__(self):
+        return iter(self.__remotes.values())
+    def __len__(self):
+        return len(self.__remotes)
+    def __getitem__(self, i):
+        return self.__remotes.values()[i]
+
+class RemotesParser(object):
+    '''
+    Parser for remote control configuration files.
+    '''
+
+    __re_vendor = re.compile(r'^#\s+brand:\s*\b(.*)\b\s*$')
+    __re_product = re.compile(r'^#\s+model\b[^:]*:\s*\b(.*)\b\s*$')
+    __re_contributor = re.compile(r'^#\s+contributed by\s+\b(.*)\b\s*$')
+    __re_block = re.compile(r'^\s*(begin|end)\s+\b(.*)\b\s*$')
+    __re_key_value = re.compile(r'^\s*(\S+)\s+\b(.*)\b\s*$')
+
+    def __init__(self):
+        self.__blocks = list()
+        self.__remotes = list()
+
+        self.__current_line = 0
+        self.__filename = None
+        self.__database = None
+
+        self.__next_remote()
+
+    def __begin_block(self, name):
+        '''
+        Track a new block beginning.
+        '''
+
+        self.__blocks.append(name)
+
+    def __end_block(self, name):
+        '''
+        Track the current block ending.
+        '''
+
+        if len(self.__blocks) and (name == self.current_block):
+            self.__blocks = self.__blocks[:-1]
+            return True
+
+        return False
+
+    def __next_remote(self):
+        '''
+        Reset all gathered remote information.
+        '''
+
+        # pylint: disable-msg=W0201
+        self.__key_codes, self.__properties = list(), list()
+        self.__vendor, self.__product = None, None
+        self.__contributor = None
+
+    def __parse_block_statements(self, line):
+        '''
+        Tries to parse the next line as block changing statement (begin/end).
+        '''
+
+        # Check if current line contains a block statement:
+        match = self.__re_block.match(line)
+
+        if not match:
+            return False
+
+        # Evaluate block statement:
+        token, name = match.groups()
+
+        if 'begin' == token:
+            self.__begin_block(name)
+            return True
+
+        assert 'end' == token
+
+        if self.__end_block(name):
+            if 'remote' == name:
+                remote = Remote(self.__database, self.__filename,
+                                self.__vendor, self.__product, self.__contributor,
+                                self.__properties, dict(self.__key_codes))
+
+                self.__remotes.append(remote)
+                self.__next_remote()
+
+            return True
+
+        raise ParseError(self.__filename, self.current_line)
+
+    def __parse_remote_block_statements(self, line):
+        '''
+        Parse a statement within a "remote" block.
+        '''
+
+        # Drop comments:
+        comment = line.find('#')
+
+        if comment >= 0:
+            line = line[:comment]
+
+        # Tokenize line:
+        match = self.__re_key_value.match(line)
+
+        if not match:
+            return False
+
+        key, value = match.groups()
+
+        # Handle tokens as required by current block:
+        if 'codes' == self.current_block:
+            self.__key_codes.append((key, value))
+            return True
+
+        if 'remote' == self.current_block:
+            self.__properties.append((key, value.split()))
+            return True
+
+    def __parse_comments(self, line):
+        '''
+        Try to extract meta information from comments.
+        '''
+
+        match = self.__re_vendor.match(line)
+
+        if match:
+            self.__vendor = match.group(1)
+            return True
+
+        match = self.__re_product.match(line)
+
+        if match:
+            self.__product = match.group(1)
+            return True
+
+        match = self.__re_contributor.match(line)
+
+        if match:
+            self.__contributor = match.group(1)
+            return True
+
+        return False
+
+    def read(self, reader, filename=None, database=None):
+        '''
+        Parses the configuration found in reader.
+        '''
+
+        if not filename:
+            # StringIO instances don't have a name attribute.
+            # Therefore use getattr to avoid AttributeErrors.
+            filename = getattr(reader, 'name', str(reader.__class__))
+
+        self.__filename = filename
+        self.__database = database
+
+        self.__current_line = 0
+
+        for line in reader:
+            self.__current_line += 1
+
+            if self.__parse_block_statements(line):
+                continue
+            if ('remote' == self.toplevel_block and
+                self.__parse_remote_block_statements(line)):
+                continue
+
+            self.__parse_comments(line)
+ 
+    def __repr__(self):
+        return repr(vars(self))
+
+    # pylint: disable-msg=W0212
+    remotes = property(lambda self: self.__remotes)
+    current_line = property(lambda self: self.__current_line)
+    current_block = property(lambda self: len(self.__blocks) and self.__blocks[-1])
+    toplevel_block = property(lambda self: len(self.__blocks) and self.__blocks[0])
+
+class KeyCodeCategory(object):
+    '''
+    Container for various key-code categories.
+    '''
+
+    DEFAULT = _('Default Namespace')
+    CUSTOM = _('Custom Key Code')
+    ELISA = _('Elisa Compatible')
+
+def create_commands_table():
+    '''
+    Create a dict mapping the command name to full details,
+    including the key name, the command name, and the human-readable translated display name.
+    This is used to
+    - create a .lircrc config file mapping all the keys to commands.
+    - discover human-readable names for commands.
+    '''
+
+    #TODO: Add other keys (and alternatives key names):
+    commands_by_category = [
+        (KeyCodeCategory.DEFAULT, (
+            ('BTN_LEFT',           'move_up_key',        _('Move Up')),
+            ('BTN_MODE',           'move_up_key',        _('Move Up')),
+            ('BTN_RIGHT',          'move_up_key',        _('Move Up')),
+            ('KEY_0',              '0_key',              _('0')),
+            ('KEY_1',              '1_key',              _('1')),
+            ('KEY_2',              '2_key',              _('2')),
+            ('KEY_3',              '3_key',              _('3')),
+            ('KEY_4',              '4_key',              _('4')),
+            ('KEY_5',              '5_key',              _('5')),
+            ('KEY_6',              '6_key',              _('6')),
+            ('KEY_7',              '7_key',              _('7')),
+            ('KEY_8',              '8_key',              _('8')),
+            ('KEY_9',              '9_key',              _('9')),
+            ('KEY_A',              'a_key',              _('A')),
+            ('KEY_AGAIN',          'again_key',          _('Again')),
+            ('KEY_ANGLE',          'angle_key',          _('Angle')),
+            ('KEY_AUDIO',          'audio_key',          _('Audio')),
+            ('KEY_AUX',            'aux_key',            _('Auxiliary')),
+            ('KEY_B',              'b_key',              _('B')),
+            ('KEY_BACK',           'back_key',           _('Back')),
+            ('KEY_BACKSPACE',      'backspace_key',      _('Backspace')),
+            ('KEY_BLUE',           'blue_key',           _('Blue')),
+            ('KEY_BOOKMARKS',      'bookmarks_key',      _('Bookmarks')),
+            ('KEY_C',              'c_key',              _('C')),
+            ('KEY_CAMERA',         'camera_key',         _('Camera')),
+            ('KEY_CANCEL',         'cancel_key',         _('Cancel')),
+            ('KEY_CD',             'cd_key',             _('CD')),
+            ('KEY_CHANNELDOWN',    'channel_down_key',   _('Channel Down')),
+            ('KEY_CHANNELUP',      'channel_up_key',     _('Channel Up')),
+            ('KEY_CLEAR',          'clear_key',          _('Clear')),
+            ('KEY_CLOSE',          'close_key',          _('Close')),
+            ('KEY_CONFIG',         'config_key',         _('Configuration')),
+            ('KEY_D',              'd_key',              _('D')),
+            ('KEY_DELETE',         'delete_key',         _('Delete')),
+            ('KEY_DIRECTORY',      'directory_key',      _('Directory')),
+            ('KEY_DOT',            'dot_key',            _('Dot')),
+            ('KEY_DOWN',           'down_key',           _('Down')),
+            ('KEY_DVD',            'dvd_key',            _('DVD')),
+            ('KEY_E',              'e_key',              _('E')),
+            ('KEY_EJECTCD',        'eject_cd_key',       _('Eject CD')),
+            ('KEY_END',            'end_key',            _('End')),
+            ('KEY_ENTER',          'enter_key',          _('Enter')),
+            ('KEY_EPG',            'epg_key',            _('EPG')),
+            ('KEY_ESC',            'esc_key',            _('Escape')),
+            ('KEY_EXIT',           'exit_key',           _('Exit')),
+            ('KEY_F',              'f_key',              _('F')),
+            ('KEY_F1',             'f1_key',             _('F1')),
+            ('KEY_F2',             'f2_key',             _('F2')),
+            ('KEY_F3',             'f3_key',             _('F3')),
+            ('KEY_F4',             'f4_key',             _('F4')),
+            ('KEY_FASTFORWARD',    'fast_forward_key',   _('Fast Forward')),
+            ('KEY_FORWARD',        'forward_key',        _('Forward')),
+            ('KEY_G',              'g_key',              _('G')),
+            ('KEY_GREEN',          'green_key',          _('Green')),
+            ('KEY_H',              'h_key',              _('H')),
+            ('KEY_HELP',           'help_key',           _('Help')),
+            ('KEY_HOME',           'home_key',           _('Home')),
+            ('KEY_INFO',           'info_key',           _('Information')),
+            ('KEY_KPASTERISK',     'kp_asteristk_key',   _('Asterisk')),
+            ('KEY_KPMINUS',        'kp_minus_key',       _('Minus')),
+            ('KEY_KPPLUS',         'kp_plus_key',        _('Plus')),
+            ('KEY_L',              'l_key',              _('L')),
+            ('KEY_LANGUAGE',       'language_key',       _('Language')),
+            ('KEY_LEFT',           'left_key',           _('Left')),
+            ('KEY_LIST',           'list_key',           _('List')),
+            ('KEY_M',              'm_key',              _('M')),
+            ('KEY_MAIL',           'mail_key',           _('Mail')),
+            ('KEY_MAX',            'max_key',            _('Maximum')),
+            ('KEY_MEDIA',          'media_key',          _('Media')),
+            ('KEY_MENU',           'menu_key',           _('Menu')),
+            ('KEY_MODE',           'mode_key',           _('Mode')),
+            ('KEY_MP3',            'mp3_key',            _('MP3')),
+            ('KEY_MUTE',           'mute_key',           _('Mute')),
+            ('KEY_NEXT',           'next_key',           _('Next')),
+            ('KEY_OK',             'ok_key',             _('OK')),
+            ('KEY_OPEN',           'open_key',           _('Open')),
+            ('KEY_OPTION',         'option_key',         _('Options')),
+            ('KEY_PAGEDOWN',       'page_down_key',      _('Page Down')),
+            ('KEY_PAGEUP',         'page_up_key',        _('Page Up')),
+            ('KEY_PAUSE',          'pause_key',          _('Pause')),
+            ('KEY_PC',             'pc_key',             _('PC')),
+            ('KEY_PHONE',          'phone_key',          _('Phone')),
+            ('KEY_PLAY',           'play_key',           _('Play')),
+            ('KEY_PLAYPAUSE',      'play_pause_key',     _('Pause')),
+            ('KEY_PLUS',           'plus_key',           _('Plus')),
+            ('KEY_POWER',          'power_key',          _('Power')),
+            ('KEY_PREVIOUS',       'previous_key',       _('Previous')),
+            ('KEY_R',              'r_key',              _('R')),
+            ('KEY_RADIO',          'radio_key',          _('Radio')),
+            ('KEY_RECORD',         'record_key',         _('Record')),
+            ('KEY_RED',            'red_key',            _('Red')),
+            ('KEY_REWIND',         'rewind_key',         _('Rewind')),
+            ('KEY_RIGHT',          'right_key',          _('Right')),
+            ('KEY_S',              's_key',              _('S')),
+            ('KEY_SELECT',         'select_key',         _('Select')),
+            ('KEY_SETUP',          'setup_key',          _('Setup')),
+            ('KEY_SLASH',          'slash_key',          _('Slash')),
+            ('KEY_SLEEP',          'sleep_key',          _('Sleep')),
+            ('KEY_SLOW',           'slow_key',           _('Slow')),
+            ('KEY_SPACE',          'space_key',          _('Space')),
+            ('KEY_STOP',           'stop_key',           _('Stop')),
+            ('KEY_SUBTITLE',       'subtitle_key',       _('Subtitle')),
+            ('KEY_T',              't_key',              _('T')),
+            ('KEY_TAB',            'tab_key',            _('Tab')),
+            ('KEY_TEXT',           'text_key',           _('Text')),
+            ('KEY_TIME',           'time_key',           _('Time')),
+            ('KEY_TITLE',          'title_key',          _('Title')),
+            ('KEY_TV',             'tv_key',             _('TV')),
+            ('KEY_UNDO',           'undo_key',           _('Undo')),
+            ('KEY_UP',             'up_key',             _('Up')),
+            ('KEY_VCR',            'vcr_key',            _('VCR')),
+            ('KEY_VIDEO',          'video_key',          _('Video')),
+            ('KEY_VOLUMEDOWN',     'volume_down_key',    _('Volume Down')),
+            ('KEY_VOLUMEUP',       'volume_up_key',      _('Volume Up')),
+            ('KEY_WWW',            'www_key',            _('WWW')),
+            ('KEY_YELLOW',         'yellow_key',         _('Yellow')),
+            ('KEY_ZOOM',           'zoom_key',           _('Zoom')),
+        )),
+
+        # Key names used by Elisa (as of 2007-02-12):
+        (KeyCodeCategory.ELISA, (
+            ('GO_UP',              'move_up_key',              _('Move Up')),
+            ('GO_DOWN',            'move_down_key',            _('Move Down')),
+            ('GO_LEFT',            'move_left_key',            _('Move Left')),
+            ('GO_RIGHT',           'move_right_key',           _('Move Right')),
+            ('MENU',               'toggle_menu_key',          _('Menu')),
+            ('OK',                 'activate_key',             _('OK')),
+            ('EXIT',               'close_key',                _('Close')),
+            ('PLAY',               'toggle_play_pause_key',    _('Play')),
+            ('PAUSE',              'pause_key',                _('Pause')),
+            ('STOP',               'stop_key',                 _('Stop')),
+            ('REC',                'record_key',               _('Record')),
+            ('INC_PLAYBACK_SPEED', 'increment_playback_speed', _('Increase Speed')),
+            ('DEC_PLAYBACK_SPEED', 'decrement_playback_speed', _('Decrease Speed')),
+            ('SEEK_FORWARD',       'seek_forward_key',         _('Seek Forward')),
+            ('SEEK_BACKWARD',      'seek_backward_key',        _('Seek Backward')),
+            ('NEXT',               'next_key',                 _('Next')),
+            ('PREVIOUS',           'previous_key',             _('Previous')),
+            ('TOGGLE_FULLSCREEN',  'toggle_fullscreen_key',    _('Full Screen')),
+            ('MUTE',               'toggle_mute_key',          _('Mute')),
+            ('VOL_UP',             'increment_volume_key',     _('Increase Volume')),
+            ('VOL_DOWN',           'decrement_volume_key',     _('Decrease Volume')),
+        )),
+
+        # Key names used by some other random lircd.conf files.
+        # TODO: These probably shouldn't be here, at least when standard key
+        # names have been agreed. Our own lircd.conf files should use only the
+        # standard names, and we should probably warn about (or ignore as
+        # broken) non-standard key names, because applications are unlikely
+        # to work with those broken lircd.conf files.
+        (KeyCodeCategory.CUSTOM, (
+            ('CH_UP',              'ch_up_key',              _('Channel Up')),
+            ('CH_DOWN',            'ch_down_key',            _('Channel Down')),
+            ('<<',                 'rewind_key',             _('Rewind')),
+            ('|<<',                'back_key',               _('Back')),
+            ('>>',                 'fast_forward_key',       _('Fast Forward')),
+            ('>>|',                'forward_key',            _('Forward')),
+            ('RECORD',             'record_key',             _('Record')),
+            ('LEFT',               'left_key',               _('Left')),
+            ('RIGHT',              'right_key',              _('Right')),
+            ('UP',                 'up_key',                 _('Up')),
+            ('DOWN',               'down_key',               _('Down')),
+        )),
+    ]
+
+    class Command(object):
+        '''
+        Description of a remote control command.
+        '''
+
+        def __init__(self, key, name, category, display_name):
+            self.__key = key
+            self.__name = name
+            self.__category = category
+            self.__display_name = display_name
+
+        # pylint: disable-msg=W0212
+        key = property(lambda self: self.__key)
+        name = property(lambda self: self.__name)
+        category = property(lambda self: self.__category)
+        display_name = property(lambda self: self.__display_name)
+
+    for i, category in enumerate(commands_by_category):
+        category_name, command_list = category
+
+        commands_by_category[i] = [
+            (key, Command(key, name, category_name, display_name))
+            for key, name, display_name in command_list]
+
+    commands = reduce(lambda a, b: a + b, commands_by_category)
+    return dict(commands), [row[1] for row in commands_by_category[0]]
+
+class KeyCodes(object):
+    '''
+    Facilites for retreiving information about remote control key codes.
+    '''
+
+    __commands, __default_commands = create_commands_table()
+
+    @classmethod
+    def get_default_commands(cls):
+        '''
+        Query all commons in the default namespace.
+        '''
+        return cls.__default_commands
+
+    @classmethod
+    def get_display_name(cls, key):
+        '''
+        Get a (translated) human-readable name for a remote control command/key.
+        For instance return "Play/Pause" for the "toggle_play_pause_key" command.
+
+        @param command: The command name
+        @result A string.
+        '''
+
+        command = cls.__commands.get(key.upper())
+
+        if command:
+            return command.display_name
+
+        return key.replace('_', ' ')
+
+    @classmethod
+    def get_category(cls, key):
+        '''
+        Get the human-readable category name for a remote control command/key.
+        '''
+
+        command = cls.__commands.get(key.upper())
+
+        if command:
+            return command.category
+
+        return _('Custom Key Code')
+
+class KeyListener(gobject.GObject):
+    '''
+    Watches remote control key presses reported by lircd.
+    '''
+
+    __gsignals__ = {
+        'key-pressed': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            (gobject.TYPE_STRING, gobject.TYPE_INT,
+             gobject.TYPE_STRING, gobject.TYPE_INT64)),
+
+        'changed': (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+            tuple()),
+    }
+
+    def __init__(self, socket_name='/dev/lircd'):
+        # pylint: disable-msg=E1002
+        super(KeyListener, self).__init__()
+
+        self.__socket_name = socket_name
+        self.__reconnect_source = 0
+        self.__socket_source = 0
+        self.__socket = None
+        self.__buffer = ''
+
+    def start(self):
+        '''
+        Starts listening.
+        '''
+
+        if not self.__connect():
+            self.__reconnect()
+
+    def __connect(self):
+        '''
+        Connects to the lircd socket.
+        '''
+
+        self.__disconnect()
+
+        logging.info('trying to connect to lircd...')
+
+        self.__socket = None
+        self.__buffer = ''
+
+        try:
+            # pylint: disable-msg=W0613
+            def on_io(fd, condition):
+                '''
+                Handle I/O events on the lircd socket.
+                '''
+
+                logging.info('I/O event on lirc socket %d: %d', fd, condition)
+
+                if condition & gobject.IO_IN:
+                    logging.info('reading from lirc socket %d...', fd)
+                    packet = self.__socket and self.__socket.recv(128)
+
+                    logging.info('...%d bytes received.', len(packet))
+                    self.__buffer += packet
+
+                    while True:
+                        eol = self.__buffer.find('\n')
+
+                        if eol < 0:
+                            break
+
+                        packet = self.__buffer[:eol]
+                        self.__buffer = self.__buffer[eol + 1:]
+
+                        logging.info('processing packet: %r', packet)
+
+                        try:
+                            code, repeat, name, remote = packet.split()
+                            repeat = int(repeat, 16)
+                            code = long(code, 16)
+
+                            # pylint: disable-msg=E1101
+                            self.emit('key-pressed', remote, repeat, name, code)
+
+                        # pylint: disable-msg=W0704
+                        except ValueError:
+                            pass
+
+                if condition & gobject.IO_HUP:
+                    self.__disconnect()
+                    return False
+
+                return True
+
+            self.__socket = socket(AF_UNIX)
+            self.__socket_source = gobject.io_add_watch(self.__socket.fileno(),
+                                                        gobject.IO_IN | gobject.IO_HUP,
+                                                        on_io)
+
+            self.__socket.connect(self.__socket_name)
+
+            logging.info('listening on %s (fd=%d, source=%d)...',
+                         self.__socket_name, self.__socket.fileno(),
+                         self.__socket_source)
+
+            # pylint: disable-msg=E1101
+            self.emit('changed')
+
+            return True
+
+        except SocketError, ex:
+            if ex[0] not in (errno.ENOENT, errno.ECONNREFUSED):
+                # pychecker says "Object (ex) has no attribute (message)", and that's not true.
+                logging.error('Cannot connect to %s: %s', self.__socket_name, ex.message)
+
+            self.__disconnect()
+            return False
+
+    def __disconnect(self):
+        '''
+        Disconnects from the lircd socket.
+        '''
+
+        self.__buffer = ''
+
+        if self.__socket_source:
+            logging.info('disconnecting source: %d', self.__socket_source)
+            gobject.source_remove(self.__socket_source)
+            self.__socket_source = 0
+
+        if self.__socket:
+            logging.info('closing socket: %d', self.__socket.fileno())
+            self.__socket.close()
+            self.__socket = None
+
+        # pylint: disable-msg=E1101
+        self.emit('changed')
+
+    def __reconnect(self):
+        '''
+        Tries to reconnects to the lircd socket.
+        '''
+
+        if not self.__reconnect_source:
+            self.__reconnect_source = gobject.timeout_add(5000, self._on_reconnect)
+
+    def _on_reconnect(self):
+        '''
+        Called regularly when the key listener tries to reconnect.
+        '''
+
+        if self.__connect():
+            self.__reconnect_source = 0
+            return False
+
+        return True
+
+    # pylint: disable-msg=W0212,W1001
+    connected = property(lambda self: None != self.__socket)
+
+class HardwareConfParser(object):
+    '''
+    Parse the (Debian/Ubuntu-specific) /etc/lirc/hardware.conf file.
+
+    Ideally, the standard RawConfigParser could do this,
+    but that does not work when there are no section headings.
+    '''
+
+    def __init__(self, filename):
+        self.__values = dict()
+
+        for line in open(filename, 'r'):
+            tokens = [t.strip() for t in line.split('=', 2)]
+
+            if len(tokens) < 2:
+                continue
+
+            key, value = tokens
+
+            value = value.strip('"') # Remove quotes
+            self.__values[key] = value
+
+    def __getitem__(self, key):
+        try:
+            return self.__values[key]
+
+        except KeyError:
+            # Try old Gutsy keys:
+            if key.startswith('RECEIVER_'):
+                return self.__getitem__(key[9:])
+            if key.startswith('REMOTE_'):
+                return self.__getitem__(key[7:])
+
+            # Ok, that key really isn't there.
+            raise
+
+    def get(self, key, default=None):
+        '''
+        Retreives the value for key, or default when the key doesn't exist.
+        '''
+
+        try:
+            return self[key]
+
+        except KeyError:
+            return default
+
+def has_include_keyword():
+    '''Checks if include statements are supported in lircd.conf.'''
+
+    return (config.LSB_RELEASE.check(name='Ubuntu', codename='hardy') or
+            config.LSB_RELEASE.check(name='Ubuntu', release='8.04'))
+
+def find_remote_config():
+    '''Finds the location our customized lircd.conf file.'''
+
+    if has_include_keyword():
+        return config.LIRC_REMOTE_CONF, True
+
+    return config.LIRC_DAEMON_CONF, False
+
+def check_hardware_settings(selected_remote):
+    '''Check if the hardware settings are sane.'''
+
+    remote_config, can_include = find_remote_config()
+
+    if can_include and not re.search(
+        r'^\s*include\s+%s\s*$' % re.escape(config.LIRC_REMOTE_CONF),
+        open(config.LIRC_DAEMON_CONF).read(), re.MULTILINE):
+        return False
+
+    parser = RemotesParser()
+    parser.read(open(remote_config))
+
+    for remote in parser.remotes:
+        if (remote.vendor == selected_remote.vendor and
+            remote.product == selected_remote.product):
+            return True
+
+    return False
+
+if '__main__' == __name__:
+    print find_remote_config()

Added: trunk/gnome_lirc_properties/lsb.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/lsb.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,79 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Linux Standard Base (LSB) releated information.
+'''
+
+from ConfigParser import SafeConfigParser
+from StringIO     import StringIO
+
+import re
+
+def version_number(text):
+    '''Parses a version number and converts it into a tuple.'''
+
+    return tuple([int(n, 10) for n in re.split(r'[^0-9]', text)])
+
+class ReleaseInfo(object):
+    '''LSB compliant distribution description.'''
+
+    def __init__(self, filename=None, fileobj=None):
+        lsb_section = 'LSB-Release'
+
+        if not fileobj:
+            fileobj = open(filename or '/etc/lsb-release')
+
+        strbuf = StringIO()
+        strbuf.write('[%s]\n' % lsb_section)
+        strbuf.write(fileobj.read())
+        strbuf.seek(0)
+
+        parser = SafeConfigParser()
+        parser.readfp(strbuf)
+
+        self.__name = parser.get(lsb_section, 'DISTRIB_ID').strip('"')
+        self.__release = parser.get(lsb_section, 'DISTRIB_RELEASE').strip('"')
+        self.__codename = parser.get(lsb_section, 'DISTRIB_CODENAME').strip('"')
+        self.__description = parser.get(lsb_section, 'DISTRIB_DESCRIPTION').strip('"')
+
+    def check(self, name=None, codename=None, release=None):
+        '''Checks if this Linux distribution matches certain criterions.'''
+
+        if name is not None and name != self.name:
+            return False
+
+        if codename is not None and codename != self.codename:
+            return False
+
+        if release is not None:
+            if version_number(release) > version_number(self.release):
+                return False
+
+        return True
+
+    def __str__(self):
+        return '%s (%s)' % (self.description, self.codename)
+
+    # pylint: disable-msg=W0212
+    name = property(lambda self: self.__name)
+    release = property(lambda self: self.__release)
+    codename = property(lambda self: self.__codename)
+    description = property(lambda self: self.__description)
+
+if '__main__' == __name__:
+    print ReleaseInfo()

Added: trunk/gnome_lirc_properties/model.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/model.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,416 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''Custom GTK tree-models.'''
+
+import gobject, gtk
+
+from gettext import gettext as _
+from gnome_lirc_properties import hardware, lirc
+
+class GenericListStore(gtk.GenericTreeModel):
+    '''Generic base class for implementing flat tree-models.'''
+
+    class RowRef(object):
+        '''Information for referencing a tree-model row.'''
+
+        def __init__(self, index, key):
+            self.__index = index
+            self.__key = key
+
+        # pylint: disable-msg=W0212
+        path = property(lambda self: (self.__index, ))
+        index = property(lambda self: self.__index)
+        key = property(lambda self: self.__key)
+
+    def __init__(self, *columns):
+        gtk.GenericTreeModel.__init__(self)
+        self.__columns = columns
+
+    # pylint: disable-msg=R0201,C0111
+    def on_get_flags(self):
+        return gtk.TREE_MODEL_LIST_ONLY
+    def on_get_n_columns(self):
+        return len(self.__columns)
+    def on_get_column_type(self, index):
+        return self.__columns[index]
+
+    def on_get_iter(self, path):
+        return self.on_iter_nth_child(None, path[0])
+    def on_get_path(self, rowref):
+        return (rowref.index, )
+
+    def on_iter_children(self, parent):
+        return self.on_iter_nth_child(parent, 0)
+    def on_iter_has_child(self, rowref):
+        return not rowref
+
+    def on_iter_next(self, rowref):
+        return self.on_iter_nth_child(None, rowref.index + 1)
+
+class DictionaryStore(GenericListStore):
+    '''Flat tree-model which uses a Python dictionary as backend.'''
+
+    COLUMN_KEY = 0
+    COLUMN_VALUE = 1
+
+    def __init__(self, values=None, compare=None,
+                 value_type=gobject.TYPE_PYOBJECT,
+                 extra_columns=()):
+        super(DictionaryStore, self).__init__(
+            gobject.TYPE_STRING, value_type,
+            *extra_columns)
+
+        self.__values = dict()
+        self.__keys = list()
+        self.__compare = compare
+
+        if values:
+            self.update(values)
+
+    def _create_path_list(self):
+        '''Creates a list of tree-model paths for all keys of this model.'''
+
+        return [(i, ) for i in range(len(self.__keys))]
+
+    def clear(self):
+        '''Removes all entries from this tree-model.'''
+
+        rows = self._create_path_list()
+        rows.reverse()
+
+        self.__keys = list()
+        self.__values.clear()
+        self.invalidate_iters()
+
+        for path in rows:
+            self.row_deleted(path)
+
+    def pop(self, key):
+        '''Removes the entry referenced by key.'''
+
+        tree_iter = self.find_iter(key)
+
+        if tree_iter is not None:
+            self.remove_iter(tree_iter)
+
+    def remove_iter(self, tree_iter):
+        '''Removes the entry referenced by tree_iter.'''
+
+        rowref = self.get_user_data(tree_iter)
+
+        self.__keys.remove(rowref.key)
+        self.__values.pop(rowref.key)
+        self.invalidate_iters()
+
+        self.row_deleted(rowref.path)
+
+    def update(self, values):
+        '''Updates the tree-model with new values.'''
+
+        old_keys = set(self.__keys)
+
+        self.__values.update(values)
+        self.__keys = list(self.__values.keys())
+        self.__keys.sort(self.__compare)
+        self.invalidate_iters()
+
+        for path in self._create_path_list():
+            tree_iter = self.get_iter(path)
+            rowref = self.get_user_data(tree_iter)
+
+            if rowref.key in old_keys:
+                self.row_changed(path, tree_iter)
+
+            else:
+                self.row_inserted(path, tree_iter)
+
+    def has_key(self, key):
+        '''Checks if the tree-model contains a certain key.'''
+
+        return self.__values.has_key(key)
+
+    def find_iter(self, key):
+        '''Tries to find the specified entry.'''
+
+        try:
+            index = self.__keys.index(key)
+            rowref = GenericListStore.RowRef(index, key)
+            return self.create_tree_iter(rowref)
+
+        except ValueError:
+            return None
+
+    def __nonzero__(self):
+        return len(self.__values) > 0
+
+    def __len__(self):
+        return len(self.__values)
+    def __getitem__(self, key):
+        return self.__values[key]
+    def __iter__(self):
+        return iter(self.__values.items())
+
+    def keys(self):
+        '''List of dictionary keys.'''
+        return self.__values.keys()
+
+    def items(self):
+        '''List of key-value tuples.'''
+        return self.__values.items()
+
+    def values(self):
+        '''List of dictionary values.'''
+        return self.__values.values()
+
+    def on_iter_n_children(self, rowref):
+        '''Returns the number of children for the specified row.'''
+
+        return not rowref and len(self.__values) or 0
+
+    def on_iter_nth_child(self, parent, index):
+        '''Retrieves the specified child node.'''
+
+        if not parent and index < len(self.__keys):
+            return self.RowRef(index, self.__keys[index])
+
+        return None
+
+    def on_get_value(self, rowref, column):
+        '''Retrieves the value stored in the specified row and column.'''
+
+        if self.COLUMN_KEY == column:
+            return rowref.key
+        if self.COLUMN_VALUE == column:
+            return self.__values[rowref.key]
+
+        return None
+
+class ReceiverVendorList(DictionaryStore):
+    '''Tree model containing all supported IR receiver vendors.'''
+
+    __str_none = _('None')
+
+    def __init__(self, hardware_manager=None):
+        def compare_values(a, b):
+            '''Compares two vendor list entries.'''
+
+            if self.__str_none == a:
+                return -1
+
+            if self.__str_none == b:
+                return +1
+
+            return cmp(a, b)
+
+        super(ReceiverVendorList, self).__init__(compare=compare_values)
+
+        self.__linux_input_devices = DictionaryStore()
+        self.update({_('Linux Input Device'): self.__linux_input_devices})
+
+        if hardware_manager:
+            hardware_manager.connect('receiver-added', self._on_receiver_added)
+            hardware_manager.connect('receiver-removed', self._on_receiver_removed)
+
+            for udi, receiver in hardware_manager.devinput_receivers.items():
+                self._on_receiver_added(hardware_manager, receiver)
+
+    def _on_receiver_added(self, sender, receiver):
+        '''Merge new hot-plugable receiver.'''
+        self.__linux_input_devices.update({receiver.product: receiver})
+
+    def _on_receiver_removed(self, sender, receiver):
+        '''Drop removed hot-plugable receiver.'''
+        self.__linux_input_devices.pop(receiver.product)
+
+    def load(self, database):
+        '''Populates the tree-model from the specified hardware database.'''
+
+        assert(isinstance(database, hardware.HardwareDatabase))
+
+        vendors = dict()
+
+        for sect in database.sections():
+            vendor_name, product_name = [s.strip() for s in sect.split(':', 1)]
+            products = vendors.get(vendor_name)
+
+            if not products:
+                vendors[vendor_name] = products = dict()
+
+            properties = dict(database.items(sect),
+                              vendor = vendor_name,
+                              product = product_name)
+
+            products[product_name] = lirc.Receiver(**properties)
+
+        vendors = [
+            (name, DictionaryStore(products))
+            for name, products in vendors.items()
+        ]
+
+        empty_store = DictionaryStore({self.__str_none: None})
+        empty_row = self.__str_none, empty_store
+        vendors.append(empty_row)
+
+        self.update(dict(vendors))
+
+class RemoteVendorList(DictionaryStore):
+    '''Tree model containing all supported IR remote vendors.'''
+
+    def load(self, database):
+        '''Populates the tree-model from the specified remotes database.'''
+
+        assert(isinstance(database, lirc.RemotesDatabase))
+
+        vendors = dict()
+
+        for remote in database:
+            vendor_name = remote.vendor or _('Unknown')
+            product_name = remote.product or remote.name
+
+            products = vendors.get(vendor_name)
+
+            if not products:
+                vendors[vendor_name] = products = dict()
+
+            products[product_name] = remote
+
+        vendors = [
+            (name, DictionaryStore(products))
+            for name, products in vendors.items()
+        ]
+
+        self.update(dict(vendors))
+
+class KeyCodeModel(DictionaryStore):
+    '''Custom tree-model for managing remote control keys.'''
+
+    COLUMN_DISPLAY_NAME = 2
+    COLUMN_CATEGORY = 3
+    COLUMN_STATE = 4
+
+    class KeyMapping(object):
+        '''Meta information around some remote control key.'''
+
+        def __init__(self, key, code = 0):
+            self.__key = key
+            self.__code = code
+            self.__state = None
+
+        def __get_state(self):
+            '''Retrieves the state of this key mapping as human-readable text.'''
+
+            if None != self.__state:
+                return self.__state
+
+            if self.__code:
+                return _('Assigned')
+
+            return _('Unassigned')
+
+        def __set_state(self, state):
+            '''Updates the state of this key mapping.'''
+
+            self.__state = state
+
+        def __cmp__(self, other):
+            assert isinstance(other, KeyCodeModel.KeyMapping)
+
+            return (
+                cmp(self.display_name, other.display_name) or
+                cmp(self.key,          other.key))
+
+        # pylint: disable-msg=W0212
+        code = property(lambda self: self.__code)
+        key = property(lambda self: self.__key.upper())
+        category = property(lambda self: lirc.KeyCodes.get_category(self.__key))
+        display_name = property(lambda self: lirc.KeyCodes.get_display_name(self.__key))
+
+        state = property(__get_state, __set_state)
+
+    def __init__(self, key_codes):
+        self.__key_codes = key_codes
+
+        # pylint: disable-msg=C0103,C0111
+        def compare_key_mappings(a, b):
+            return cmp(self[a], self[b])
+
+        columns = gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING
+        super(KeyCodeModel, self).__init__(key_codes, extra_columns=columns,
+                                           compare=compare_key_mappings)
+
+    @classmethod
+    def __map_key_codes(cls, key_codes):
+        '''Maps raw key-codes to KeyMapping objects.'''
+
+        return dict([
+            (key.upper(), cls.KeyMapping(key, code))
+            for key, code in key_codes.items() or []])
+
+    def on_get_value(self, rowref, column):
+        '''Retrieves the value stored in the specified row and column.'''
+
+        if self.COLUMN_DISPLAY_NAME == column:
+            return self[rowref.key].display_name
+        if self.COLUMN_CATEGORY == column:
+            return self[rowref.key].category
+        if self.COLUMN_STATE == column:
+            return self[rowref.key].state
+
+        return super(KeyCodeModel, self).on_get_value(rowref, column)
+
+    def set_state(self, path, state):
+        '''Changes the state of the row referenced by path.'''
+
+        tree_iter = self.get_iter(path)
+        rowref = self.get_user_data(tree_iter)
+        self[rowref.key].state = state
+
+        self.row_changed(path, tree_iter)
+
+    def remove_iter(self, tree_iter):
+        '''Removes the entry referenced by tree_iter.'''
+
+        rowref = self.get_user_data(tree_iter)
+        self.__key_codes.pop(rowref.key)
+
+        super(KeyCodeModel, self).remove_iter(tree_iter)
+
+    def update(self, key_codes):
+        '''Update the scan codes for several remote control keys.'''
+
+        key_codes = dict(key_codes)
+        self.__key_codes.update(key_codes)
+        key_codes = self.__map_key_codes(key_codes)
+        super(KeyCodeModel, self).update(key_codes)
+
+    def update_key(self, key, code=0):
+        '''Update the scan code for a certain remote control key.'''
+
+        self.update([(key, code)])
+
+    def count_keys(self, category):
+        '''Counts the keys in the specified categories.'''
+
+        count = 0
+
+        for name in self.__key_codes.keys():
+            if lirc.KeyCodes.get_category(name) == category:
+                count += 1
+
+        return count
+

Added: trunk/gnome_lirc_properties/net/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/net/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,6 @@
+moduledir = $(pythondir)/gnome_lirc_properties/net
+
+module_PYTHON = \
+	__init__.py \
+	MultipartPostHandler.py \
+	services.py

Added: trunk/gnome_lirc_properties/net/MultipartPostHandler.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/net/MultipartPostHandler.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,175 @@
+####
+# 02/2006 Will Holcomb <wholcomb gmail com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+"""
+Usage:
+  Enables the use of multipart/form-data for posting forms
+
+Inspirations:
+  Upload files in python:
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
+  urllib2_file:
+    Fabien Seisen: <fabien seisen org>
+
+Example:
+  import MultipartPostHandler, urllib2, cookielib
+
+  cookies = cookielib.CookieJar()
+  opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
+                                MultipartPostHandler.MultipartPostHandler)
+  params = { "username" : "bob", "password" : "riviera",
+             "file" : open("filename", "rb") }
+  opener.open("http://wwww.bobsite.com/upload/";, params)
+
+Further Example:
+  The main function of this file is a sample which downloads a page and
+  then uploads it to the W3C validator.
+"""
+
+import urllib
+import urllib2
+import mimetools, mimetypes
+import os, sys
+
+class MultipartPostHandler(urllib2.BaseHandler):
+    """
+    URL handler for posting multipart/form-data forms.
+    """
+
+    handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
+
+    def __init__(self):
+        pass
+
+    def http_request(self, request):
+        """
+        Handles a HTTP request.
+        """
+
+        data = request.get_data()
+
+        if data is not None and type(data) != str:
+            v_files = []
+            v_vars = []
+
+            try:
+                for key, value in data.items():
+                    if hasattr(value, 'read'):
+                        v_files.append((key, value))
+
+                    else:
+                        v_vars.append((key, value))
+
+            except TypeError:
+                traceback = sys.exc_info()[2]
+                raise TypeError, "not a valid non-string sequence or mapping object", traceback
+
+            if len(v_files) == 0:
+                data = urllib.urlencode(v_vars, True)
+
+            else:
+                boundary, data = self.multipart_encode(v_vars, v_files)
+                content_type = 'multipart/form-data; boundary=%s' % boundary
+
+                if (request.has_header('Content-Type') and
+                    request.get_header('Content-Type').find('multipart/form-data') != 0):
+
+                    print "Replacing %s with %s" % (
+                        request.get_header('content-type'),
+                        'multipart/form-data')
+
+                request.add_unredirected_header('Content-Type', content_type)
+
+            request.add_data(data)
+
+        return request
+
+    @staticmethod
+    def multipart_encode(fields, files, boundary = None, output = None):
+        """
+        Encodes a multipart request body.
+        """
+
+        if boundary is None:
+            boundary = mimetools.choose_boundary()
+
+        if output is None:
+            output = ''
+
+        for key, value in fields:
+            output += '--%s\r\n' % boundary
+            output += 'Content-Disposition: form-data; name="%s"' % key
+            output += '\r\n\r\n' + value + '\r\n'
+
+        for key, fd in files:
+            filename = os.path.basename(fd.name)
+
+            content_type = (
+                mimetypes.guess_type(filename)[0] or
+                'application/octet-stream')
+
+            output += '--%s\r\n' % boundary
+            output += 'Content-Disposition: form-data; '
+            output += 'name="%s"; ' % urllib.quote(key)
+            output += 'filename="%s"\r\n' % urllib.quote(filename)
+            output += 'Content-Type: %s\r\n' % content_type
+
+            # file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
+            # output += 'Content-Length: %s\r\n' % file_size
+
+            fd.seek(0)
+
+            output += '\r\n' + fd.read() + '\r\n'
+
+        output += '--%s--\r\n\r\n' % boundary
+
+        return boundary, output
+
+    https_request = http_request
+
+def main():
+    '''
+    Entry point for testing this class.
+    '''
+
+    validator_url = "http://validator.w3.org/check";
+    opener = urllib2.build_opener(MultipartPostHandler)
+
+    def validate_file(url):
+        '''
+        Validates the specified page at the W3C validator.
+        '''
+
+        import tempfile
+
+        temp = tempfile.mkstemp(suffix=".html")
+        os.write(temp[0], opener.open(url).read())
+
+        params = {
+            "ss" : "0",            # show source
+            "doctype" : "Inline",
+            "uploaded_file" : open(temp[1], "rb"),
+        }
+
+        print opener.open(validator_url, params).read()
+        os.remove(temp[1])
+
+    if len(sys.argv[1:]) > 0:
+        for arg in sys.argv[1:]:
+            validate_file(arg)
+
+    else:
+        validate_file("http://www.google.com";)
+
+if __name__ == "__main__":
+    main()

Added: trunk/gnome_lirc_properties/net/__init__.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/net/__init__.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,23 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Various file network services.
+'''
+
+# pylint: disable-msg=W0401
+from gnome_lirc_properties.net.services import *

Added: trunk/gnome_lirc_properties/net/services.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/net/services.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,294 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Download and update services.
+'''
+
+import gobject, httplib, locale, logging
+import md5, os, re, rfc822, sha, time, urllib2
+
+from gettext   import gettext as _
+from StringIO  import StringIO
+from tempfile  import NamedTemporaryFile
+from threading import Thread
+
+from gnome_lirc_properties.net.MultipartPostHandler import MultipartPostHandler
+
+__re_html_response__ = re.compile(r'<h1>(.*?)</h1>\s*<p>(.*?)</p>', re.DOTALL)
+
+class NetworkError(Exception):
+    '''Exception for network related problems.'''
+
+    def __init__(self, context, cause):
+        self.details = self.extract_details(cause)
+        self.context = context
+        self.cause = cause
+
+        if self.details:
+            message = '%s: %s.' % (context, self.details)
+
+        else:
+            message = '%s.' % context
+
+        super(NetworkError, self).__init__(message)
+
+    @staticmethod
+    def extract_details(ex):
+        '''Extract error details from various network related exception types.'''
+
+        if isinstance(ex, urllib2.HTTPError):
+            return (extract_html_message(ex) or ex.msg)
+
+        if isinstance(ex, urllib2.URLError):
+            from socket import gaierror
+
+            if isinstance(ex.reason, gaierror):
+                return ex.reason[1]
+
+            return _('Cannot resolve host name')
+
+        return getattr(ex, 'message', None)
+
+def report(callback, *args, **kwargs):
+    '''Invokes the callback in within the main-thread.'''
+
+    if callback:
+        gobject.idle_add(lambda: callback(*args, **kwargs) and False)
+
+def extract_html_message(response):
+    '''Extracts the first paragraph of a HTML document.'''
+
+    match = __re_html_response__.search(response.read())
+    return match and match.group(2)
+
+# pylint: disable-msg=R0913
+def post_file(target_uri, filename,
+              context, content=None,
+              finished_callback=None,
+              failure_callback=logging.error):
+    '''Uploads a certain file to some web service.'''
+    # TODO: Maybe use GObject signals instead of callbacks.
+
+    if content is None:
+        fileobj = open(filename, 'rb')
+
+    else:
+        fileobj = StringIO(content)
+        setattr(fileobj, 'name', filename)
+
+    params = {
+        'config': fileobj,
+        'digest': sha.new(fileobj.read()).hexdigest(),
+        'locale': locale.setlocale(locale.LC_MESSAGES, None),
+    }
+
+    fileobj.seek(0)
+
+    # We use a custom OpenerDirector, instead of just using urllib2.openurl(),
+    # so we can specify handlers to use our cookies store, and multipart posts:
+    multipart_opener = urllib2.build_opener(MultipartPostHandler)
+
+    # Passing a request body makes this a POST instead of a GET request:
+
+    try:
+        response = multipart_opener.open(target_uri, params)
+
+        if finished_callback:
+            finished_callback(extract_html_message(response) or
+                              (_('Upload of %s succeeded.') % context))
+
+    except (urllib2.HTTPError, urllib2.URLError), ex:
+        if failure_callback:
+            error = NetworkError(_('Upload of %s failed') % context, ex)
+            failure_callback(error.message)
+
+class RetrieveTarballThread(Thread):
+    '''Worker thread for retrieving tarballs.'''
+
+    def __init__(self, tarball_uri, checksum_uri=None):
+        super(RetrieveTarballThread, self).__init__()
+
+        last_slash = tarball_uri.rindex('/')
+        self.__basename = tarball_uri[last_slash + 1:]
+        self.__baseuri = tarball_uri[:last_slash + 1]
+
+        if not checksum_uri:
+            dot = self.__basename.index('.')
+            checksum_uri = self.__baseuri + self.__basename[:dot] + '.md5sum'
+
+        self.__tarball_uri  = tarball_uri
+        self.__checksum_uri = checksum_uri
+        self.__action_label = None
+        self.__tempfile = None
+
+        self.on_progress    = None
+        self.on_success     = None
+        self.on_failure     = logging.error
+        self.reference_time = None
+
+    def _retrieve(self, request, target=None):
+        '''Reads from a file-like object, and reports progress to the main thread.'''
+
+        response = urllib2.urlopen(request)
+        headers = response.info()
+
+        if target is None:
+            target = StringIO()
+
+        if self.on_progress is None:
+            target.write(response.read())
+
+        else:
+            file_size = int(headers.get('content-length', '-1'))
+            offset = 0
+
+            while True:
+                chunk = response.read(8192)
+
+                if not chunk:
+                    break
+
+                offset += len(chunk)
+                target.write(chunk)
+
+                report(self.on_progress, offset, file_size, self.__action_label)
+
+                from time import sleep
+                sleep(0.1)
+
+        target.seek(0)
+
+        return target, headers
+
+    def _retrieve_checksum(self):
+        '''Retrieves the checksum file associated with the tarball.'''
+
+        try:
+            self.__action_label = _('Downloading checksum list...')
+            content, headers = self._retrieve(self.__checksum_uri)
+
+        except (urllib2.HTTPError, urllib2.URLError), ex:
+            raise NetworkError(_('Cannot retrieve checksum list'), ex)
+
+        if 'text/plain' != headers['content-type']:
+            raise NetworkError(_('Cannot retrieve checksum list'),
+                               _('Unexpected content type.'))
+
+        checksums = dict([
+            reversed(line.rstrip().split('  '))
+            for line in content.readlines()])
+
+        return checksums.get(self.__basename)
+
+    def _retrieve_archive(self):
+        '''Retrieves the file archive.'''
+
+        try:
+            self.__action_label = _('Downloading file archive...')
+
+            request = urllib2.Request(self.__tarball_uri)
+
+            if self.reference_time is not None:
+                timestamp = time.ctime(self.reference_time)
+                request.add_header('If-Modified-Since', timestamp)
+
+            self.__tempfile = NamedTemporaryFile(prefix='remote-updates-', suffix='.tar.gz')
+            return self._retrieve(request, target=self.__tempfile)
+
+        except urllib2.HTTPError, ex:
+            if httplib.NOT_MODIFIED != ex.code:
+                raise NetworkError(_('Cannot retrieve file archive'), ex)
+
+            elif self.on_success:
+                report(self.on_success, ex, ex.info())
+
+        except urllib2.URLError, ex:
+            raise NetworkError(_('Cannot retrieve file archive'), ex)
+
+        return None, None
+
+    def run(self):
+        '''Entry point of the worker thread.'''
+
+        try:
+            # Retrieve the file archive:
+            content, headers = self._retrieve_archive()
+
+            if content is None:
+                return True
+
+            # Update last-modified timestamp from response header:
+            timestamp = headers.get('last-modified')
+
+            if timestamp:
+                timestamp = time.mktime(rfc822.parsedate(timestamp))
+                os.utime(self.__tempfile.name, (timestamp, timestamp))
+
+            # Check that the file's content matches the provided checksum:
+            return self._verify_checksum(content, headers)
+
+        except NetworkError, ex:
+            report(self.on_failure, ex.message)
+            return False
+
+    def _verify_checksum(self, content, headers):
+        '''Check that the file's content matches the provided checksum.'''
+
+        expected_checksum = headers.get('x-checksum-sha1')
+
+        if expected_checksum is None:
+            expected_checksum = self._retrieve_checksum()
+        if expected_checksum is None:
+            report(self.on_failure, _('Checksum for %s not found.') % self.__basename)
+
+        if expected_checksum:
+            checksum_algorithm = {32: md5, 40: sha}[len(expected_checksum)]
+            checksum = checksum_algorithm.new(content.read())
+
+            content.seek(0)
+
+            if checksum.hexdigest() == expected_checksum:
+                report(self.on_success, content, headers)
+                return True
+
+        report(self.on_failure, _('Checksum for %s doesn\'t match.') % self.__basename)
+        return False
+
+# pylint: disable-msg=R0913
+def retrieve_tarball(tarball_uri,
+                     checksum_uri=None,
+                     reference_time=None,
+                     progress_callback=None,
+                     success_callback=None,
+                     failure_callback=logging.error):
+    '''Downloads a certain tarball from the internet.'''
+    # TODO: Maybe use GObject signals instead of callbacks.
+
+    worker = RetrieveTarballThread(tarball_uri, checksum_uri)
+
+    worker.on_progress = progress_callback
+    worker.on_success  = success_callback
+    worker.on_failure  = failure_callback
+
+    if reference_time is not None:
+        worker.reference_time = reference_time
+
+    worker.start()
+
+__all__ = 'post_file', 'retrieve_tarball'
+

Added: trunk/gnome_lirc_properties/policykit.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/policykit.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,80 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+PolicyKit related services.
+'''
+
+import dbus, logging, os
+
+from gnome_lirc_properties import config
+
+class PolicyKitAuthentication(object):
+    '''
+    Obtains sudo/root access,
+    asking the user for authentication if necessary,
+    using PolicyKit
+    '''
+
+    def is_authorized(self, action_id=config.POLICY_KIT_ACTION):
+        '''
+        Ask PolicyKit whether we are already authorized.
+        '''
+
+        if not config.ENABLE_POLICY_KIT:
+            return True
+
+        # Check whether the process is authorized:
+        pid = dbus.UInt32(os.getpid())
+        authorized = self.policy_kit.IsProcessAuthorized(action_id, pid, False)
+        logging.debug('%s: authorized=%r', action_id, authorized)
+
+        return ('no' != authorized)
+
+    def obtain_authorization(self, widget, action_id=config.POLICY_KIT_ACTION):
+        '''
+        Try to obtain authoriztation for the specified action.
+        '''
+
+        if not config.ENABLE_POLICY_KIT:
+            return True
+
+        xid = (widget and widget.get_toplevel().window.xid or 0)
+        xid, pid = dbus.UInt32(xid), dbus.UInt32(os.getpid())
+
+        granted = self.auth_agent.ObtainAuthorization(action_id, xid, pid)
+        logging.debug('%s: granted=%r', action_id, granted)
+
+        return bool(granted)
+
+    def __get_policy_kit(self):
+        '''Retreive the D-Bus interface of PolicyKit.'''
+
+        # retreiving the interface raises DBusException on error:
+        service = dbus.SystemBus().get_object('org.freedesktop.PolicyKit', '/')
+        return dbus.Interface(service, 'org.freedesktop.PolicyKit')
+
+    def __get_auth_agent(self):
+        '''Retreive the D-Bus interface of the PolicyKit authentication agent.'''
+
+        # retreiving the interface raises DBusException on error:
+        return dbus.SessionBus().get_object(
+            'org.freedesktop.PolicyKit.AuthenticationAgent', '/',
+            'org.gnome.PolicyKit.AuthorizationManager.SingleInstance')
+
+    auth_agent = property(__get_auth_agent)
+    policy_kit = property(__get_policy_kit)

Added: trunk/gnome_lirc_properties/ui/CustomConfiguration.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/CustomConfiguration.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,728 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+The dialog for customizing remote control settings.
+'''
+
+import dbus, logging, gobject, gtk
+
+from gettext                         import gettext as _
+from gnome_lirc_properties           import backend, config, lirc, model, net
+from gnome_lirc_properties.ui.common import show_message, thread_callback
+from StringIO                        import StringIO
+from time                            import time
+
+class CustomConfiguration(object):
+    '''This window allows the user to detect each key individually.'''
+
+    default_keys = model.DictionaryStore(
+        values=dict([(c.key, c.display_name) for c in
+                     lirc.KeyCodes.get_default_commands()]),
+        value_type=gobject.TYPE_STRING)
+
+    def __init__(self, glade_xml):
+        self.__remote = None
+
+        # irrecord drivers:
+        self.__detection_driver = None
+        self.__learning_driver = None
+
+        # Remember row of column that currently learned:
+        self.__learning_timeout = 0
+        self.__learning_row = None
+
+        # setup widgets
+        self.__setup_ui(glade_xml)
+
+    # pylint: disable-msg=W0201
+    def __setup_ui(self, glade_xml):
+        '''Initialize widgets.'''
+
+        self.__ui = glade_xml
+        self.__ui.signal_autoconnect(self)
+
+        # lookup major widgets:
+        self.__dialog   = self.__ui.get_widget('custom_configuration')
+        self.__notebook = self.__ui.get_widget('notebook')
+
+        # lookup buttons:
+        self.__button_ok            = self.__ui.get_widget('button_ok')
+        self.__button_upload        = self.__ui.get_widget('button_upload')
+        self.__button_detect_basics = self.__ui.get_widget('button_detect_basics')
+        self.__button_keys_learn    = self.__ui.get_widget('button_keys_learn')
+        self.__button_keys_remove   = self.__ui.get_widget('button_keys_remove')
+        self.__button_keys_clear    = self.__ui.get_widget('button_keys_clear')
+        self.__button_keys_add      = self.__ui.get_widget('button_keys_add')
+
+        # setup model page:
+        self.__entry_vendor      = self.__ui.get_widget('entry_vendor')
+        self.__entry_product     = self.__ui.get_widget('entry_product')
+        self.__entry_contributor = self.__ui.get_widget('entry_contributor')
+        self.__usage_hint        = self.__ui.get_widget('usage_hint')
+
+        size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+        size_group.add_widget(self.__entry_vendor)
+        size_group.add_widget(self.__usage_hint)
+
+        # setup remaining notebook pages:
+        self.__setup_basics()
+        self.__setup_keys()
+
+        # apply "secondary" child property: for some reason libglade ignores it
+        self.__dialog.action_area.set_child_secondary(self.__button_upload, True)
+
+        # apply stock icon to learn button
+        image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_RECORD,
+                                         gtk.ICON_SIZE_BUTTON)
+        self.__button_keys_learn.set_image(image)
+
+    # pylint: disable-msg=W0201
+    def __setup_basics(self):
+        '''Initialize widgets of the "Basics" page.'''
+
+        self.__page_basics               = self.__ui.get_widget('page_basics')
+        self.__treeview_basics           = self.__ui.get_widget('treeview_basics')
+        self.__progressbar_detect_basics = self.__ui.get_widget('progressbar_detect_basics')
+
+        treeview_columns = (
+            gtk.TreeViewColumn(_('Property'), gtk.CellRendererText(), text=0),
+            gtk.TreeViewColumn(_('Value'), gtk.CellRendererText(), text=1),
+        )
+
+        for column in treeview_columns:
+            self.__treeview_basics.append_column(column)
+
+        selection = self.__treeview_basics.get_selection()
+        selection.set_mode(gtk.SELECTION_BROWSE)
+
+    # pylint: disable-msg=W0201
+    def __setup_keys(self):
+        '''Initialize widgets of the "Keys" page.'''
+
+        self.__page_keys       = self.__ui.get_widget('page_keys')
+        self.__treeview_keys   = self.__ui.get_widget('treeview_keys')
+        self.__hbuttonbox_keys = self.__ui.get_widget('hbuttonbox_keys')
+        self.__label_keys_hint = self.__ui.get_widget('label_keys_hint')
+        self.__image_keys_hint = self.__ui.get_widget('image_keys_hint')
+
+        self.__keys_learning_hint = _(
+            '<b>Learning new key code.</b>\n'
+            'Press the button on your remote control which should emit this key-code.')
+        self.__keys_default_hint = self.__label_keys_hint.get_label()
+
+        # pylint: disable-msg=W0613
+        def editing_started(cell, editable, path):
+            '''
+            Replace the human-friendly key name by the internal key-name,
+            before editing a cell in the key-name column.
+            '''
+
+            keys_model = self.__treeview_keys.get_model()
+            key, = keys_model.get(keys_model.get_iter(path),
+                                  keys_model.COLUMN_KEY)
+
+            editable.child.set_text(key)
+
+        # pylint: disable-msg=W0613
+        def edited(cell, path, new_name):
+            '''Update the keys model, when editing of the key-name column has ended.'''
+
+            keys_model = self.__treeview_keys.get_model()
+            keys_iter = keys_model.get_iter(path)
+
+            old_name, mapping = keys_model.get(keys_iter,
+                                               keys_model.COLUMN_KEY,
+                                               keys_model.COLUMN_VALUE)
+
+            if old_name != new_name and not keys_model.has_key(new_name):
+                keys_model.remove_iter(keys_iter)
+                keys_model.update_key(new_name, mapping.code)
+                self.select_key(new_name)
+
+            self.__treeview_keys.grab_focus()
+
+        keys_renderer = gtk.CellRendererCombo()
+        keys_renderer.set_properties(editable=True, text_column=0,
+                                     model=self.default_keys)
+
+        keys_renderer.connect('edited', edited)
+        keys_renderer.connect('editing-started', editing_started)
+
+        text_renderer = gtk.CellRendererText()
+        text_renderer.set_properties(xalign=0.5)
+
+        treeview_columns = (
+            gtk.TreeViewColumn(_('Name'), keys_renderer,
+                               text=model.KeyCodeModel.COLUMN_DISPLAY_NAME),
+
+            gtk.TreeViewColumn(_('Category'), text_renderer,
+                               text=model.KeyCodeModel.COLUMN_CATEGORY),
+
+            gtk.TreeViewColumn(_('State'), text_renderer,
+                               markup=model.KeyCodeModel.COLUMN_STATE),
+        )
+
+        for column in treeview_columns:
+            self.__treeview_keys.append_column(column)
+
+        selection = self.__treeview_keys.get_selection()
+        selection.connect('changed', self._on_treeview_keys_selection_changed)
+        selection.set_mode(gtk.SELECTION_BROWSE)
+
+    def __update_basics_model(self, properties):
+        '''Update the entries stored in the basic properties model.'''
+
+        if not properties:
+            properties = dict()
+
+        basics = dict([(key, ' '.join(value)) for key, value in properties.items()])
+        tree_model_basics = model.DictionaryStore(basics, value_type=gobject.TYPE_STRING)
+        self.__treeview_basics.set_model(tree_model_basics)
+
+    def __apply_hardware(self, receiver, device, remote):
+        '''
+        Select some remote for configuration.
+        Information about the receiver is needed for managing lircd.
+        '''
+
+        if not remote:
+            remote = lirc.Remote()
+
+        self.__receiver, self.__device = receiver, device
+        self.__remote = remote
+
+        # Update custom model page from remote
+        self.vendor_name = remote.vendor or ''
+        self.product_name = remote.product or ''
+        self.contributor_name = remote.contributor or ''
+
+        # Update basic configuration page from remote
+        self.__update_basics_model(remote.properties)
+
+        # Update keys page from remote
+        tree_model = model.KeyCodeModel(remote.key_codes)
+        self.__treeview_keys.set_model(tree_model)
+
+        # Update widget sensitivity
+
+        self._on_dialog_changed()
+
+    def _on_learning_timeout(self):
+        '''
+        Timeout callback for animating the currently selected row,
+        when learning key codes.
+        '''
+
+        if not self.__learning_row:
+            # Abort animation, if now row is selected for learning:
+            self.__learning_timeout = 0
+            return False
+
+        # Calculate color for blinking "Learning" text:
+
+        p = abs(time() % 2 - 1)/2.0 + 0.5
+        q = 1 - p
+
+        style = self.__treeview_keys.get_style()
+
+        text = style.text[gtk.STATE_SELECTED]
+        text = [p * color for color in text.red, text.green, text.blue]
+
+        base = style.base[gtk.STATE_SELECTED]
+        base = [q * color for color in base.red, base.green, base.blue]
+
+        # Apply blinking "Learning" text:
+
+        args = [int((a + b)/256) for a, b in zip(text, base)]
+        args.append(_('Learning'))
+
+        text = '<span weight="bold" foreground="#%02x%02x%02x">%s</span>' % tuple(args)
+
+        keys = self.__treeview_keys.get_model()
+        keys.set_state(self.__learning_row.get_path(), text)
+
+        # Continue animation:
+        return True
+
+    def __start_learning(self, path):
+        '''Start learning of a remote control's key code.'''
+
+        keys = self.__treeview_keys.get_model()
+
+        if self.__learning_row:
+            keys.set_state(self.__learning_row.get_path(), None)
+        if not self.__learning_timeout:
+            self.__learning_timeout = gobject.timeout_add(100, self._on_learning_timeout)
+
+        self.__learning_row = gtk.TreeRowReference(keys, path)
+        mapping, = keys.get(keys.get_iter(path), 1)
+
+        configuration = self.__remote.configuration
+        driver = self.__receiver.lirc_driver or ''
+        device = self.__device or ''
+
+        # pylint: disable-msg=W0613
+        @thread_callback
+        def report_success(result, sender=None):
+            '''Handle success reported by the service backend.'''
+
+            self.__learning_driver = None
+            self.__stop_learning()
+
+            hwdb = lirc.RemotesDatabase()
+            hwdb.read(StringIO(result))
+            remote = len(hwdb) and hwdb[0]
+
+            if remote:
+                self.__treeview_keys.get_model().update(remote.key_codes)
+
+        # pylint: disable-msg=W0613
+        @thread_callback
+        def report_failure(message, sender=None):
+            '''Handle failures reported by the service backend.'''
+
+            show_message(self.__dialog, _('Learning of Key Code Failed'), message)
+
+            self.__learning_driver = None
+            self.__stop_learning()
+
+        try:
+            bus     = backend.get_service_bus()
+            service = backend.get_service(bus)
+
+            # Stop the lirc deamon, as some drivers (e.g. lirc_streamzap) do
+            # not support concurrent device access:
+            service.ManageLircDaemon('stop')
+
+            driver  = service.LearnKeyCode(driver, device, configuration, [mapping.key])
+
+            driver = bus.get_object(service.requested_bus_name, driver)
+            driver = dbus.Interface(driver, backend.ExternalToolDriver.INTERFACE_NAME)
+
+            self.__learning_driver = driver
+
+            driver.connect_to_signal('ReportSuccess', report_success)
+            driver.connect_to_signal('ReportFailure', report_failure)
+
+            driver.Execute()
+
+        except dbus.DBusException, ex:
+            report_failure.callback(ex.message)
+
+        self._on_dialog_changed()
+
+    def __stop_learning(self):
+        '''Stop learning of a remote control's key code.'''
+
+        if self.__learning_driver:
+            try:
+                self.__learning_driver.Release()
+
+            except dbus.DBusException, ex:
+                logging.error(ex)
+
+            try:
+                # restart the lirc deamon (we stopped it before learning):
+                backend.get_service().ManageLircDaemon('start')
+
+            except dbus.DBusException, ex:
+                logging.error(ex)
+
+            self.__learning_driver = None
+
+        if self.__learning_row:
+            keys = self.__treeview_keys.get_model()
+            keys.set_state(self.__learning_row.get_path(), None)
+            self.__learning_row = None
+
+        self._on_dialog_changed()
+
+    # pylint: disable-msg=C0103,W0613
+    def _on_treeview_keys_selection_changed(self, selection):
+        '''Handle selection of row in the "keys" tree view.'''
+        self._on_dialog_changed()
+        self.__stop_learning()
+
+    # pylint: disable-msg=W0613
+    def _on_treeview_keys_row_activated(self, view, path, column):
+        '''Handle activation of row in the "keys" tree view.'''
+        self.__start_learning(path)
+
+    def _on_button_upload_clicked(self, button):
+        '''Handle the "Upload" button's "clicked" signal.'''
+
+        def configuration_problem(message):
+            '''
+            Informs the user about configuration file problems,
+            and ask the user for accption or rejection.
+            '''
+
+            response_buttons = (
+                (gtk.RESPONSE_ACCEPT, _('_Upload Configuration')),
+                (gtk.RESPONSE_REJECT, gtk.STOCK_CANCEL),
+            )
+
+            message = '%s %s' % (message, _('Do you really want to upload this configuration?'))
+
+            return (gtk.RESPONSE_ACCEPT != show_message(
+                    self.__dialog, _('Configuration Problems'),
+                    message, buttons=response_buttons))
+
+        def on_upload_finished(message):
+            '''Informs the user about succeeding uploads.'''
+
+            show_message(self.__dialog, _('Upload Succeeded'),
+                         message, message_type=gtk.MESSAGE_INFO)
+            self.__dialog.set_sensitive(True)
+
+        def on_upload_failure(message):
+            '''Informs the user about upload failures.'''
+
+            show_message(self.__dialog, _('Upload Failed'), message)
+            self.__dialog.set_sensitive(True)
+
+        keys_model = self.__treeview_keys.get_model()
+
+        if 0 == keys_model.count_keys(lirc.KeyCodeCategory.DEFAULT):
+            if configuration_problem(_('This configuration has no keys for the default ' +
+                                       'namespace. Most applications won\'t be able to ' +
+                                       'use this configuration.')):
+                return
+
+        elif 0 != keys_model.count_keys(lirc.KeyCodeCategory.CUSTOM):
+            if configuration_problem(_('Some keys in this configuration have names ' +
+                                       'which do not belong to any standardized namespace.' +
+                                       'Most applications won\'t be able to use those keys.')):
+                return
+
+        self.__dialog.set_sensitive(False)
+
+        net.post_file(target_uri=config.URI_UPLOADS,
+                      filename=lirc.find_remote_config()[0],
+                      content=self.__remote.configuration,
+                      context=_('customized configuration file'),
+                      finished_callback=on_upload_finished,
+                      failure_callback=on_upload_failure)
+
+    def __retreive_remote_name(self):
+        '''Build the symbolic name of the currently edited remote.'''
+
+        name = self.__remote and self.__remote.name
+
+        if not name:
+            pieces = [s.replace(' ', '-') for s in self.vendor_name, self.product_name]
+            name = '_'.join(pieces)
+
+        return name
+
+    def _on_detect_button_clicked(self, button):
+        '''Handle the "Detect" button's "clicked" signal.'''
+
+        @thread_callback
+        def report_failure(message):
+            '''Handle failures reported by the service backend.'''
+
+            self.__progressbar_detect_basics.hide()
+            show_message(self.__dialog, _('Remote Configuration Failed'), message)
+
+            if service:
+                service.ManageLircDaemon('start')
+
+        @thread_callback
+        def report_success(result, sender=None):
+            '''Handle success reported by the service backend.'''
+
+            self.__progressbar_detect_basics.hide()
+
+            hwdb = lirc.RemotesDatabase()
+            hwdb.read(StringIO(result))
+
+            remote = hwdb[0]
+            remote.properties['name'] = [self.__retreive_remote_name()]
+
+            self.__remote.properties = remote.properties
+            self.__update_basics_model(self.__remote.properties)
+
+            if service:
+                service.ManageLircDaemon('start')
+
+        @thread_callback
+        def report_progress(sender=None):
+            '''Handle progress reported by the service backend.'''
+
+            self.__progressbar_detect_basics.pulse()
+
+        @thread_callback
+        def request_action(message, details=None, sender=None):
+            '''Forward actions requests from the service backend to the user.'''
+
+            self.__progressbar_detect_basics.set_text(message)
+            self.__progressbar_detect_basics.set_fraction(0)
+
+            if details:
+                # TODO: This dialog should probably have a cancel button.
+                response_buttons = (
+                    (gtk.RESPONSE_ACCEPT, _('_Start'), gtk.STOCK_EXECUTE),
+                )
+
+                show_message(self.__dialog, message, details,
+                             message_type=gtk.MESSAGE_INFO,
+                             buttons=response_buttons)
+
+            driver.Proceed()
+
+        bus, service, driver = None, None, None
+
+        try:
+            self.__stop_detection()
+
+            bus     = backend.get_service_bus()
+            service = backend.get_service(bus)
+
+            driver = service.DetectParameters(self.__receiver.lirc_driver or '',
+                                              self.__device or '')
+
+            driver = bus.get_object(service.requested_bus_name, driver)
+            driver = dbus.Interface(driver, backend.ExternalToolDriver.INTERFACE_NAME)
+
+            driver.connect_to_signal('RequestAction',  request_action)
+            driver.connect_to_signal('ReportProgress', report_progress)
+            driver.connect_to_signal('ReportSuccess',  report_success)
+            driver.connect_to_signal('ReportFailure',  report_failure)
+
+            # TODO: Stop the key-listener, when we know that lircd is disabled.
+            service.ManageLircDaemon('stop')
+
+            self.__detection_driver = driver
+            self.__detection_driver.Execute()
+
+            self.__progressbar_detect_basics.show()
+
+        except dbus.DBusException, ex:
+            report_failure.callback(ex.message)
+            self.__stop_detection()
+
+    def __stop_detection(self):
+        '''Stop the basic properties detection driver.'''
+
+        try:
+            if self.__detection_driver:
+                self.__detection_driver.Release()
+                self.__detection_driver = None
+
+        except dbus.DBusException, ex:
+            logging.warning(ex)
+
+    def _on_dialog_changed(self, widget = None):
+        '''Handle major changes to the dialog.'''
+
+        # Calculate completion state
+
+        have_receiver = (None != self.__receiver)
+        have_basics = bool(self.__treeview_basics.get_model())
+        have_keys = bool(self.__treeview_keys.get_model())
+        is_learning = self.__learning_row is not None
+
+        model_complete = (bool(self.vendor_name) and bool(self.product_name))
+        basics_complete = (model_complete and have_basics)
+        keys_complete = (basics_complete and have_keys)
+
+        selection = self.__treeview_keys.get_selection()
+        keys_iter = selection.get_selected()[1]
+
+        # Update state of global widgets:
+        self.__button_ok.set_sensitive(model_complete)
+
+        # Update state of model-page widgets:
+        self.__usage_hint.set_property('visible', not model_complete)
+        self.__button_upload.set_sensitive(keys_complete)
+
+        # Update state of basics-page widgets:
+        self.__treeview_basics.set_sensitive(have_receiver)
+        self.__button_detect_basics.set_sensitive(have_receiver)
+
+        # Update state of keys-page widgets:
+        self.__treeview_keys.set_sensitive(basics_complete and have_receiver)
+        self.__hbuttonbox_keys.set_sensitive(basics_complete and have_receiver)
+
+        self.__button_keys_add.set_sensitive(not is_learning)
+        self.__button_keys_remove.set_sensitive(have_keys and not is_learning)
+        self.__button_keys_clear.set_sensitive(have_keys and not is_learning)
+        self.__button_keys_learn.set_sensitive(keys_iter is not None)
+        self.__button_keys_learn.set_active(is_learning)
+
+        self.__label_keys_hint.set_markup(is_learning and
+                                          self.__keys_learning_hint or
+                                          self.__keys_default_hint)
+
+        self.__image_keys_hint.set_property('visible', is_learning)
+
+    def select_key(self, key_name):
+        '''Select the row of the specified key.'''
+
+        keys_model = self.__treeview_keys.get_model()
+        tree_iter = keys_model.find_iter(key_name)
+        path = None
+
+        if tree_iter:
+            # Select the key...
+            selection = self.__treeview_keys.get_selection()
+            selection.select_iter(tree_iter)
+
+            # ...and make sure that it's visible:
+            path = keys_model.get_path(tree_iter)
+            self.__treeview_keys.scroll_to_cell(path)
+
+
+        return path
+
+    # pylint: disable-msg=W0613
+    def _on_button_keys_add_clicked(self, button):
+        '''Handle the "Add" button's "clicked" signal.'''
+
+        def find_next_key():
+            '''Find the next unassigned key.'''
+
+            # Figure out if one of the default keys is missing:
+
+            for key in default_keys:
+                if key not in current_keys:
+                    return key
+
+            # Otherwise generate a key name:
+
+            i = 0
+            while True:
+                key = 'KEY_%d' % i
+
+                if not key in current_keys:
+                    return key
+
+                i += 1
+
+        selection = self.__treeview_keys.get_selection()
+        keys_model, tree_iter = selection.get_selected()
+
+        # pylint: disable-msg=W0612
+        current_keys = [mapping.key.upper() for name, mapping in keys_model]
+        default_keys = [command.key.upper() for command in
+                        lirc.KeyCodes.get_default_commands()]
+
+        if tree_iter:
+            # See which key is selected and reorder the default_keys list,
+            # that one of its successor will be suggested.
+            selected_key, = keys_model.get(tree_iter, 0)
+
+            try:
+                i = default_keys.index(selected_key.upper())
+                default_keys = default_keys[i+1:] + default_keys[:i+1]
+
+            # pylint: disable-msg=W0704
+            except ValueError:
+                pass
+
+        # Insert another key to the keys model:
+        next_key = find_next_key()
+        keys_model.update_key(next_key)
+        path = self.select_key(next_key)
+
+        self._on_dialog_changed()
+        self.__start_learning(path)
+
+    def _on_button_keys_remove_clicked(self, button):
+        '''Handle the "Remove" button's "clicked" signal.'''
+
+        selection = self.__treeview_keys.get_selection()
+        keys_model, tree_iter = selection.get_selected()
+        index, = keys_model.get_path(tree_iter)
+
+        keys_model.remove_iter(tree_iter)
+
+        path = min(index, len(keys_model) - 1),
+        selection.select_path(path)
+        self._on_dialog_changed()
+
+    def _on_button_keys_learn_toggled(self, button):
+        '''Handle the "Learn Key Code" button's "clicked" signal.'''
+
+        if self.__button_keys_learn.get_active():
+            selection = self.__treeview_keys.get_selection()
+            keys_model, keys_iter = selection.get_selected()
+            path = keys_model.get_path(keys_iter)
+            self.__start_learning(path)
+
+        else:
+            self.__stop_learning()
+
+    def _on_button_keys_clear_clicked(self, button):
+        '''Handle the "Clear" button's "clicked" signal.'''
+
+        self.__treeview_keys.get_model().clear()
+        self._on_dialog_changed()
+
+    def _on_close(self, dialog):
+        '''Handle the dialog's "close" signal.'''
+
+        self.__stop_detection()
+        self.__stop_learning()
+
+        # Prevent that pygtk destroys the dialog on ESC:
+        dialog.hide()
+
+        return True
+
+    def _on_response(self, dialog, response):
+        '''Handle the dialog's "response" signal.'''
+
+        if gtk.RESPONSE_OK == response:
+            try:
+                service = backend.get_service()
+                self.__remote.update_configuration(service)
+                service.ManageLircDaemon('restart')
+
+            except dbus.DBusException, ex:
+                show_message(self.__dialog,
+                             _('Cannot Save Custom Configuration'),
+                             ex.message)
+
+                dialog.stop_emission('response')
+
+        elif response > 0:
+            dialog.stop_emission('response')
+
+    def run(self, receiver, device, remote):
+        '''Show the dialog and return when the window is hidden.'''
+
+        self.__apply_hardware(receiver, device, remote)
+        self.__notebook.set_current_page(0)
+        self.__entry_vendor.grab_focus()
+
+        self.__dialog.run()
+        self.__dialog.hide()
+
+    # pylint: disable-msg=W0212
+    vendor_name = property(
+        lambda self: self.__entry_vendor.get_text().strip(),
+        lambda self, value: self.__entry_vendor.set_text(value))
+    product_name = property(
+        lambda self: self.__entry_product.get_text().strip(),
+        lambda self, value: self.__entry_product.set_text(value))
+    contributor_name = property(
+        lambda self: self.__entry_contributor.get_text().strip(),
+        lambda self, value: self.__entry_contributor.set_text(value))
+

Added: trunk/gnome_lirc_properties/ui/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,10 @@
+moduledir = $(pythondir)/gnome_lirc_properties/ui
+
+module_PYTHON = \
+	__init__.py \
+	common.py \
+	CustomConfiguration.py \
+	ProgressWindow.py \
+	ReceiverChooserDialog.py \
+	RemoteControlProperties.py
+

Added: trunk/gnome_lirc_properties/ui/ProgressWindow.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/ProgressWindow.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,99 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Progress window related code.
+'''
+
+import gobject
+
+from gettext import gettext as _
+
+class ProgressWindow(object):
+    '''
+    A window for showing progress of lengthly operations.
+    '''
+
+    def __init__(self, glade_xml):
+        self.__window       = glade_xml.get_widget('progress_window')
+        self.__label_title  = glade_xml.get_widget('label_progress_title')
+        self.__label_detail = glade_xml.get_widget('label_progress_detail')
+        self.__progressbar  = glade_xml.get_widget('progressbar')
+
+    def show(self, parent, title, detail = None):
+        '''
+        Shows the progress window and updates the message it shows.
+        '''
+
+        self.title = title
+        self.detail = detail or _('Preparing...')
+
+        self.__window.set_transient_for(parent)
+        self.__window.show()
+
+    def hide(self):
+        '''
+        Hides the progress window.
+        '''
+
+        self.__window.hide()
+
+    def update(self, progress, total, message):
+        '''
+        Updates progress bar and progress message.
+        '''
+
+        if total > 0:
+            self.__progressbar.set_fraction(min(1.0, float(progress)/float(total)))
+        else:
+            self.__progressbar.pulse()
+
+        self.__progressbar.set_text(message or '')
+
+    def __get_title(self):
+        '''
+        Queries the current window title.
+        '''
+
+        return self.__label_title.get_text()
+
+    def __set_title(self, title):
+        '''
+        Updates the current window title.
+        '''
+
+        markup = gobject.markup_escape_text(title)
+        markup = '<big><b>%s</b></big>' % markup
+        self.__label_title.set_markup(markup)
+
+    def __get_detail(self):
+        '''
+        Queries the current detail text.
+        '''
+
+        return self.__label_detail.get_text()
+
+    def __set_detail(self, detail):
+        '''
+        Updates the current detail text.
+        '''
+
+        self.__label_detail.set_text(detail)
+
+    title = property(__get_title, __set_title)
+    detail = property(__get_detail, __set_detail)
+

Added: trunk/gnome_lirc_properties/ui/ReceiverChooserDialog.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/ReceiverChooserDialog.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,163 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+A dialog for choosing among multiple detected IR receivers.
+'''
+
+import gobject, gtk, pango
+
+class ReceiverChooserModel(gtk.ListStore):
+    '''
+    Tree-model for listing auto-detected IR receivers.
+    '''
+
+    COLUMN_MARKUP, COLUMN_RECEIVER, COLUMN_UDI, COLUMN_DEVICE = range(4)
+
+    def __init__(self):
+        super(ReceiverChooserModel, self).__init__(gobject.TYPE_STRING,
+                                                   gobject.TYPE_PYOBJECT,
+                                                   gobject.TYPE_STRING,
+                                                   gobject.TYPE_STRING)
+
+        self.set_sort_column_id(self.COLUMN_MARKUP, gtk.SORT_ASCENDING)
+
+    def append_receiver(self, receiver, udi, device, product_name=None):
+        '''
+        Appends a receiver to this list-store.
+        '''
+
+        if product_name is None:
+            args = receiver.vendor, receiver.product
+
+        else:
+            args = receiver.product, product_name
+
+        args = tuple(map(gobject.markup_escape_text, args))
+        markup = '%s <b>%s</b>' % args
+
+        self.set(self.append(),
+                 self.COLUMN_MARKUP,   markup,
+                 self.COLUMN_RECEIVER, receiver,
+                 self.COLUMN_UDI,      udi,
+                 self.COLUMN_DEVICE,   device)
+
+    def __len__(self):
+        return self.iter_n_children(None)
+
+class ReceiverChooserDialog(object):
+    '''
+    Dialog for choosing from auto-detected IR receivers.
+    '''
+
+    def __init__(self, glade_xml):
+        super(ReceiverChooserDialog, self).__init__()
+
+        # initialize attributes
+        self.__dialog        = glade_xml.get_widget('receiver_chooser_dialog')
+        self.__button_accept = glade_xml.get_widget('receiver_chooser_accept')
+        self.__receiver_view = glade_xml.get_widget('receiver_view')
+        self.__receivers     = ReceiverChooserModel()
+
+        # auto-connect signal handlers
+        glade_xml.signal_autoconnect(self)
+
+        # setup the tree view
+        renderer = gtk.CellRendererText()
+        renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
+        column = gtk.TreeViewColumn('', renderer, markup=0)
+
+        self.__receiver_view.set_model(self.__receivers)
+        self.__receiver_view.append_column(column)
+
+        selection = self.__receiver_view.get_selection()
+        selection.connect('changed', self._on_selection_changed)
+        selection.set_mode(gtk.SELECTION_BROWSE)
+
+    def reset(self):
+        '''
+        Restores the initial state of this dialog's widgets.
+        '''
+
+        self.__button_accept.set_sensitive(False)
+        self.__receivers.clear()
+
+    def show(self):
+        '''
+        Shows this dialog.
+        '''
+
+        self.__dialog.show()
+
+    def append(self, receiver, udi, device, product_name=None):
+        '''
+        Appends another receiver to this chooser,
+        and returns the current number of receivers shown.
+        '''
+
+        self.__receivers.append_receiver(receiver, udi, device, product_name)
+        self.__receiver_view.set_cursor((0, ), None, True)
+
+        return len(self.__receivers)
+
+    # pylint: disable-msg=R0201,W0613
+    def _on_receiver_view_row_activated(self, view, path, column):
+        '''
+        Handles activation of tree-view rows by closing the dialog
+        with gtk.RESPONSE_ACCEPT.
+        '''
+
+        view.get_toplevel().response(gtk.RESPONSE_ACCEPT)
+
+    def _on_selection_changed(self, selection):
+        '''
+        Activates the "Accept" button, when a receiver is selected.
+        '''
+
+        rows = selection.count_selected_rows()
+        self.__button_accept.set_sensitive(rows > 0)
+
+    def _on_delete_event(self, dialog, event):
+        '''
+        Prevent dialog destruction on ESC.
+        '''
+
+        dialog.hide()
+        return True
+
+    def __get_selected_receiver(self):
+        '''
+        Retrieves the currently selected receiver and its device node.
+        '''
+
+        selection = self.__receiver_view.get_selection()
+        tree_model, tree_iter = selection.get_selected()
+
+        return tree_model.get(tree_iter,
+                              tree_model.COLUMN_RECEIVER,
+                              tree_model.COLUMN_DEVICE)
+
+    def __get_n_receivers(self):
+        '''
+        Retreives the current number of receivers.
+        '''
+
+        return len(self.__receivers)
+
+    selected_receiver = property(__get_selected_receiver)
+    n_receivers = property(__get_n_receivers)
+

Added: trunk/gnome_lirc_properties/ui/RemoteControlProperties.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/RemoteControlProperties.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,1085 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+The main window of the application.
+'''
+
+import dbus, errno, gobject, gtk, gtk.gdk, gtk.glade, pango
+import httplib, locale, logging, os, subprocess
+
+from gettext               import gettext as _
+from gnome_lirc_properties import backend, config, lirc, model, hardware, policykit, net
+
+from gnome_lirc_properties.ui.common                import show_message, thread_callback
+from gnome_lirc_properties.ui.CustomConfiguration   import CustomConfiguration
+from gnome_lirc_properties.ui.ProgressWindow        import ProgressWindow
+from gnome_lirc_properties.ui.ReceiverChooserDialog import ReceiverChooserDialog
+
+class RemoteControlProperties(object):
+    '''The main window.'''
+
+    def __init__(self, glade_xml):
+        # Prevent UI changes from being written back to configuration files:
+        #
+        # Configuration files only are written when this field is zero.
+        # Negative values completely lock this field. Non-negative values can
+        # be modified with paired(!) calls to _begin_update_configuration()
+        # and end_update_configuration().
+        self.__configuration_level = -1
+
+        # Initialize models and views.
+        self.__ui = glade_xml
+        self.__ui.signal_autoconnect(self)
+
+        self.__custom_configuration = None
+        self.__receiver_chooser = None
+        self.__lookup_widgets()
+
+        self.__setup_models()
+        self.__setup_key_listener()
+        self.__setup_product_lists()
+        self.__setup_authorization()
+        self.__setup_size_groups()
+
+        # Look in the configuration to show previously-chosen details:
+        self.__restore_hardware_settings()
+
+        # Allow UI changes from being written back to configuration files:
+        self.__configuration_level = 0
+
+    def __setup_models(self):
+        '''Initialize model objects of the dialog.'''
+
+        # pylint: disable-msg=W0201,E1101
+
+        receivers_db = hardware.HardwareDatabase(self.__ui.relative_file('receivers.conf'))
+        self.__remotes_db = lirc.RemotesDatabase()
+
+        self.__hardware_manager = hardware.HardwareManager(receivers_db)
+        self.__hardware_manager.connect('search-progress', self._on_search_progress)
+        self.__hardware_manager.connect('search-finished', self._on_search_finished)
+        self.__hardware_manager.connect('receiver-found',  self._on_receiver_found)
+
+        self.__receiver_vendors = model.ReceiverVendorList(self.__hardware_manager)
+        self.__receiver_vendors.load(receivers_db)
+
+        self.__remote_vendors = model.RemoteVendorList()
+        self.__update_remotes_db()
+
+    def __update_remotes_db(self):
+        '''Fills the database of remote controls with information.'''
+
+        was_using_supplied = self.use_supplied_remote
+        selected_remote = self.selected_remote
+
+        self.__remotes_db.clear()
+        self.__remotes_db.load(self.__ui.relative_file('linux-input-layer-lircd.conf'))
+        self.__remotes_db.load_folder()
+        self.__remotes_db.load_tarball()
+
+        self.__remote_vendors.clear()
+        self.__remote_vendors.load(self.__remotes_db)
+
+        if 0 == self.__configuration_level:
+            # pylint: disable-msg=E1103
+
+            self.selected_remote = (
+                (was_using_supplied and self.supplied_remote) or
+                (selected_remote and self.__remotes_db.get(selected_remote.name)))
+
+    def __setup_key_listener(self):
+        '''Initialize the key-listener and related widgets.'''
+
+        # pylint: disable-msg=W0201,E1101
+
+        self.__key_listener = lirc.KeyListener()
+        self.__key_listener.connect('changed',     self.__on_lirc_changed)
+        self.__key_listener.connect('key-pressed', self.__on_lirc_key_pressed)
+
+    def __lookup_widgets(self):
+        '''Initialize widget attributes from Glade file.'''
+
+        # This method is more robust than looking up and assigning widgets
+        # manually, but it also completly screws up pychecker and pylint. For
+        # pylint we ship a plugin to fix this.
+
+        # pylint: disable-msg=W0201
+
+        widget_list = (
+            # receiver widgets:
+            'table_receiver_selection',
+            'combo_receiver_product_list',
+            'combo_receiver_vendor_list',
+
+            'alignment_auto_detect',
+            'hbox_auto_detect_progress',
+            'progressbar_auto_detect',
+
+            'label_device',
+            'combo_device',
+            'label_device_name',
+            'spinbutton_device',
+
+            # remote widgets:
+            'combo_remote_product_list',
+            'combo_remote_vendor_list',
+
+            'radiobutton_supplied_remote',
+            'radiobutton_other_remote',
+            'alignment_remote_selection',
+            'button_download',
+
+            # preview widgets:
+            'label_preview_status',
+            'label_preview_result',
+        )
+
+        for widget_id in widget_list:
+            attr = '_%s__%s' % (self.__class__.__name__, widget_id)
+            widget = self.__ui.get_widget(widget_id)
+            assert widget is not None, widget_id
+            setattr(self, attr, widget)
+
+        self.__dialog = self.__ui.get_widget('lirc_properties_dialog')
+        self.__entry_device = self.__combo_device.get_child()
+        self.__table_receiver_selection.set_row_spacing(2, 0)
+
+    def __setup_size_groups(self):
+        '''
+        Create some size-groups to ensure that the dialog keeps its size,
+        when sub-widgets change visibility.
+        '''
+
+        size_group = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
+        size_group.add_widget(self.__spinbutton_device.get_parent())
+        size_group.add_widget(self.__combo_device)
+
+        size_group = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
+        size_group.add_widget(self.__hbox_auto_detect_progress)
+        size_group.add_widget(self.__alignment_auto_detect)
+
+        size_group = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
+        size_group.add_widget(self.__label_preview_status)
+        size_group.add_widget(self.__label_preview_result)
+
+    def __setup_product_lists(self):
+        '''Initialize widgets with product listings.'''
+
+        def vendor_has_products(model, iter):
+            products, = model.get(iter, 1)
+            return len(products) > 0
+
+        vendor_list = self.__receiver_vendors.filter_new()
+        vendor_list.set_visible_func(vendor_has_products)
+
+        self.__combo_receiver_vendor_list.set_model(vendor_list)
+        self.__combo_receiver_vendor_list.set_active(0)
+
+        self.__combo_remote_vendor_list.set_model(self.__remote_vendors)
+        self.__combo_remote_vendor_list.set_active(0)
+
+        # update widget sensitivity
+        self._on_radiobutton_supplied_remote_toggled()
+
+        # apply ellipses to combo boxes
+        for widget in (
+            self.__combo_receiver_vendor_list, self.__combo_receiver_product_list,
+            self.__combo_remote_vendor_list, self.__combo_remote_product_list):
+            widget.get_cells()[0].set_property('ellipsize', pango.ELLIPSIZE_END)
+
+    def __setup_authorization(self):
+        '''Initialize authorization facilities.'''
+
+        # pylint: disable-msg=W0201
+
+        self.__auth = policykit.PolicyKitAuthentication()
+
+        # Discover whether PolicyKit has already given us authorization
+        # (without asking the user) so we can unlock the UI at startup if
+        # necessary:
+        granted = self.__auth.is_authorized()
+        self._set_widgets_locked(not granted)
+
+    def __confirm_rewrite_configuration(self, remote):
+        '''Ask the user if the configuration files should be rewritten.'''
+
+        if not remote:
+            show_message(self.__dialog,
+                _('Invalid IR Configuration'),
+                _('Your configuration files seems to be incorrect.'),
+                gtk.BUTTONS_OK)
+
+            return False
+
+        responses = (
+            (gtk.RESPONSE_REJECT, _('_Keep Configuration'), gtk.STOCK_CANCEL),
+            (gtk.RESPONSE_ACCEPT, _('_Restore Configuration'), gtk.STOCK_REDO),
+        )
+
+        return (
+            gtk.RESPONSE_ACCEPT == show_message(self.__dialog,
+            _('Invalid IR Configuration'),
+            _('Your configuration files seems to be incorrect. ' +
+              'Should this program try to restore your settings, ' +
+              'for a %s %s remote?') % (
+            remote.vendor, remote.product), buttons=responses))
+
+    def __restore_hardware_settings(self):
+        '''Restore hardware settings from configuration files.'''
+
+        # We really do not want to rewrite any configuration files
+        # at that stage, so __configuration_level should be non-zero.
+        assert 0 != self.__configuration_level
+
+        # Read settings from hardware.conf:
+        settings = lirc.HardwareConfParser(config.LIRC_HARDWARE_CONF)
+
+        # Practice some sanity checks on that file:
+        remote = self.__remotes_db.find(settings.get('REMOTE_VENDOR'),
+                                        settings.get('REMOTE_MODEL'))
+
+        if (not lirc.check_hardware_settings(remote) and
+            self.__confirm_rewrite_configuration(remote)):
+            try:
+                service = backend.get_service()
+
+                remote.update_configuration(service)
+
+                service.ManageLircDaemon('enable')
+                service.ManageLircDaemon('restart')
+
+            except dbus.DBusException, e:
+                show_message(self.__dialog,
+                             _('Cannot restore IR configuration'),
+                             _('Backend failed: %s') % e.message)
+
+        # Try to select configured receiver vendor:
+        #
+        # NOTE: Ubuntu's lirc script doesn't distinguish between remote and
+        # receiver settings in "hardware.conf", whereas we have to. For that
+        # reason the device node's name is stored in REMOTE_DEVICE, instead
+        # of RECEIVER_DEVICE.
+        #
+        self.selected_receiver = (
+            settings.get('RECEIVER_VENDOR'),
+            settings.get('RECEIVER_MODEL'),
+            settings.get('REMOTE_DEVICE'),
+        )
+
+        # Try to select configured remote vendor:
+        self.selected_remote = (
+            settings.get('REMOTE_VENDOR'),
+            settings.get('REMOTE_MODEL'),
+        )
+
+        # Toggle radio buttons to show if restored remote is supplied remote:
+        self.use_supplied_remote = (self.supplied_remote == self.selected_remote)
+
+    def __on_lirc_changed(self, listener):
+        '''Handle state changes of the LIRC key listener.'''
+
+        if listener.connected:
+            self.__label_preview_result.show()
+            self.__label_preview_result.set_text(_('<none>'))
+            self.__label_preview_status.set_text(_('Press remote control buttons to test:'))
+
+        else:
+            self.__label_preview_result.hide()
+            self.__label_preview_status.set_markup(_('<b>Warning:</b> Remote control daemon ' +
+                                                     'not running. Cannot test buttons.'))
+
+    # pylint: disable-msg=W0613,R0913
+    def __on_lirc_key_pressed(self, listener, remote, repeat, name, code):
+        '''Handle key presses reported by the LIRC key listener.'''
+
+        display_name = lirc.KeyCodes.get_display_name(name)
+        category = lirc.KeyCodes.get_category(name)
+
+        args = map(gobject.markup_escape_text, (display_name, category))
+        markup = '<b>%s</b><small> (%s)</small>' % tuple(args)
+
+        self.__label_preview_result.set_markup(markup)
+
+    # pylint: disable-msg=C0103
+    def _on_radiobutton_supplied_remote_toggled(self, radio_button=None):
+        '''
+        Gray-out the custom IR remote control widgets if the user wants to use
+        the remote control supplied with the IR receiver:
+        '''
+
+        # Find remote selection widgets:
+        remote_selection_widgets = []
+
+        for child in self.__alignment_remote_selection.child.get_children():
+            if isinstance(child, gtk.Box):
+                remote_selection_widgets.extend(child.get_children())
+
+            else:
+                remote_selection_widgets.append(child)
+
+        # Update sensitivity of remote selection widgets:
+        use_supplied = self.use_supplied_remote
+
+        for child in remote_selection_widgets:
+            if child is not self.__button_download:
+                child.set_sensitive(not use_supplied)
+
+        # Choose supplied remote when requested:
+        if use_supplied and self.supplied_remote:
+            self.selected_remote = self.supplied_remote
+
+    def _on_button_download_clicked(self, button=None):
+        '''Handle clicks on the auto-detection button.'''
+
+        @thread_callback
+        def on_download_progress(progress, total, action):
+            '''Handle progress reports from download service.'''
+
+            args = (
+                locale.format(percent='%.1f', grouping=True, value=progress/1024.0),
+                locale.format(percent='%.1f', grouping=True, value=total/1024.0))
+            message = (
+                total > 0 and _('%s of %s KiB retrieved...') % args
+                           or _('%s KiB retrieved...') % args[0])
+
+            progress_window.detail = action
+            progress_window.update(progress, total, message)
+
+        @thread_callback
+        def on_download_success(content, headers):
+            '''Handle finished downloads.'''
+
+            self.__dialog.set_sensitive(True)
+            progress_window.hide()
+
+            if 'application/x-gzip' == headers.get('content-type'):
+                try:
+                    backend.get_service().InstallRemoteDatabase(content.name)
+                    self.__update_remotes_db()
+
+                except dbus.DBusException, ex:
+                    show_message(self.__dialog, progress_window.title, ex.message)
+
+            elif httplib.NOT_MODIFIED == getattr(content, 'code', None):
+                show_message(self.__dialog, progress_window.title,
+                             details=_('No updates available. Your remote control configuration ' +
+                                       'files are already up-to-date.'),
+                             message_type=gtk.MESSAGE_INFO)
+
+            else:
+                show_message(self.__dialog, progress_window.title,
+                             _('Download of updated remote control configurations failed.'))
+
+        @thread_callback
+        def on_download_failure(message):
+            '''Handle failure reports from download service.'''
+
+            self.__dialog.set_sensitive(True)
+            progress_window.hide()
+
+            show_message(self.__dialog, progress_window.title, message)
+
+        self.__dialog.set_sensitive(False)
+        progress_window = ProgressWindow(self.__ui)
+        progress_window.show(self.__dialog, _('Updating Remote Configuration Files'))
+
+        gtk.gdk.threads_leave()
+
+        try:
+            timestamp = (
+                os.path.isfile(config.LIRC_REMOTES_TARBALL) and
+                os.path.getmtime(config.LIRC_REMOTES_TARBALL) or
+                None)
+
+            net.retrieve_tarball(tarball_uri=config.URI_UPDATES,
+                                 progress_callback=on_download_progress,
+                                 success_callback=on_download_success,
+                                 failure_callback=on_download_failure,
+                                 reference_time=timestamp)
+
+        finally:
+            gtk.gdk.threads_enter()
+
+    # pylint: disable-msg=C0103
+    def _on_radiobutton_other_remote_size_allocate(self, widget, alloc):
+        '''
+        Keep padding of remote properties alignment consistent
+        with the padding implied by the radio buttons.
+        '''
+
+        xpad = widget.get_child().allocation.x - widget.allocation.x
+        self.__alignment_remote_selection.set_padding(0, 0, xpad, 0)
+
+    def _on_vendor_list_changed(self, vendor_list):
+        '''
+        Change the combobox to show the list of models for the selected 
+        manufacturer:
+        '''
+
+        tree_iter = vendor_list.get_active_iter()
+
+        if not tree_iter:
+            vendor_list.set_active(0)
+            return
+
+        vendors = vendor_list.get_model()
+        products, = vendors.get(tree_iter, 1)
+
+        self.__combo_receiver_product_list.set_model(products)
+        self.__combo_receiver_product_list.set_active(0)
+
+    def __setup_devices_model(self, device_nodes):
+        '''Populate the combo box for device with device nodes.'''
+
+        self.__combo_device.show()
+        self.__spinbutton_device.hide()
+        self.__label_device.set_text_with_mnemonic(_('_Device:'))
+        self.__label_device.set_mnemonic_widget(self.__combo_device)
+
+        if device_nodes:
+            # populate device combo's item list
+            device_node_model = self.__combo_device.get_model()
+
+            if device_node_model is None:
+                device_node_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+                device_node_model.set_sort_column_id(0, gtk.SORT_ASCENDING)
+                self.__combo_device.set_model(device_node_model)
+                self.__combo_device.set_text_column(0)
+
+                self.__combo_device.clear()
+
+                renderer = gtk.CellRendererText()
+                self.__combo_device.pack_start(renderer, True)
+                self.__combo_device.set_attributes(renderer, markup=2)
+
+            else:
+                device_node_model.clear()
+
+            for device in device_nodes:
+                if isinstance(device, tuple):
+                    args = map(gobject.markup_escape_text, reversed(device))
+                    markup = '<b>%s</b>\n<small>%s</small>' % tuple(args)
+                    name, device = device
+
+                else:
+                    name, markup = '', gobject.markup_escape_text(device)
+
+                tree_iter = device_node_model.append()
+                device_node_model.set(tree_iter, 0, device, 1, name, 2, markup)
+
+            # activate first device node
+            self.__combo_device.set_sensitive(True)
+            self.__combo_device.set_active(0)
+
+        else:
+            # deactivate combo box, if device is not configurable
+            self.__combo_device.set_sensitive(False)
+            self.selected_device = None
+
+
+    def __setup_numeric_device(self, device_node):
+        '''Initialize the spin-button for numeric device selection.'''
+
+        device_node = self.__hardware_manager.parse_numeric_device_node(device_node)
+        label, value, minimum, maximum = device_node
+
+        label = label and '%s:' % label or _('_Device:')
+
+        self.__combo_device.hide()
+        self.__spinbutton_device.show()
+
+        self.__label_device.set_text_with_mnemonic(label)
+        self.__label_device.set_mnemonic_widget(self.__spinbutton_device)
+
+        self.__spinbutton_device.set_range(minimum, maximum)
+        self.__spinbutton_device.set_value(value)
+
+        self._on_spinbutton_device_value_changed()
+
+    def _on_combo_device_changed(self, combo=None):
+        '''Handle changes to device combo-box.'''
+
+        receiver = self.selected_receiver
+        device = self.selected_device
+
+        self.selected_receiver = receiver, device
+
+        tree_model = self.__combo_device.get_model()
+        tree_iter = self.__combo_device.get_active_iter()
+
+        device_name = (
+            tree_model is not None and tree_iter is not None and
+            tree_model.get(tree_iter, 1)[0] or '')
+
+        markup = gobject.markup_escape_text(device_name)
+        self.__label_device_name.set_markup('<small>%s</small>' % markup)
+
+    # pylint: disable-msg=C0103,W0613
+    def _on_spinbutton_device_value_changed(self, spinbutton=None):
+        '''Handle changes to the spin-button for numeric device selection.'''
+
+        self.selected_device = ('%d' % self.__spinbutton_device.get_value())
+
+    def _on_product_list_changed(self, product_list):
+        '''Handle selection changes to receiver product list.'''
+
+        # lookup selection:
+        tree_iter = product_list.get_active_iter()
+
+        if tree_iter is None:
+            product_list.set_active(0)
+            return
+
+        receiver, = product_list.get_model().get(tree_iter, 1)
+
+        #  freeze configuration updates:
+        self._begin_update_configuration()
+
+        try:
+            # resolve device nodes:
+            device_nodes = (receiver and receiver.device_nodes or [])
+
+            if device_nodes:
+                device_nodes = [node.strip() for node in device_nodes.split(',')]
+                device_nodes = self.__hardware_manager.resolve_device_nodes(device_nodes)
+
+            if (1 == len(device_nodes) and
+                isinstance(device_nodes[0], str) and
+                device_nodes[0].startswith('numeric:')):
+                # show some spinbutton, when the device node requires numeric input
+                self.__setup_numeric_device(device_nodes[0])
+
+            else:
+                # show the combobox, when device nodes can be choosen from list
+                self.__setup_devices_model(device_nodes)
+
+            # highlight supplied IR remote:
+            if self.supplied_remote:
+                self.selected_remote = self.supplied_remote
+
+        finally:
+            #  thaw configuration updates:
+            self._end_update_configuration(receiver, self.selected_device)
+
+    def _on_remote_vendor_list_changed(self, vendor_list):
+        '''Handle selection changes to remote vendor list.'''
+
+        tree_iter = vendor_list.get_active_iter()
+
+        if tree_iter:
+            products, = vendor_list.get_model().get(tree_iter, 1)
+            self.__combo_remote_product_list.set_model(products)
+            self.__combo_remote_product_list.set_active(0)
+
+    def _on_remote_product_list_changed(self, product_list):
+        '''Handle selection changes to remote product list.'''
+
+        tree_iter = product_list.get_active_iter()
+        remote, = product_list.get_model().get(tree_iter, 1)
+
+        if remote:
+            self._begin_update_configuration()
+            self._end_update_configuration(remote)
+
+    def _on_search_progress(self, manager, fraction, message):
+        '''Handle progress reports from the auto-detection process.'''
+
+        self.__table_receiver_selection.set_sensitive(False)
+        self.__hbox_auto_detect_progress.show()
+        self.__alignment_auto_detect.hide()
+
+        if fraction >= 0:
+            self.__progressbar_auto_detect.set_fraction(fraction)
+
+        # gtk.ProgressBar doesn't support any text padding.
+        # Work around that visual glitch with spaces.
+        self.__progressbar_auto_detect.set_text(' %s ' % message)
+
+    def _on_search_finished(self, manager = None):
+        '''Handle end of the auto-detection process.'''
+
+        self.__table_receiver_selection.set_sensitive(True)
+        self.__hbox_auto_detect_progress.hide()
+        self.__alignment_auto_detect.show()
+
+        n_receivers = self.__receiver_chooser.n_receivers
+
+        if 1 == n_receivers:
+            self._select_chosen_receiver()
+
+        elif 0 == n_receivers:
+            self.__combo_receiver_vendor_list.set_active(0)
+
+            responses = (
+                (gtk.RESPONSE_CANCEL, gtk.STOCK_CANCEL),
+                (gtk.RESPONSE_REJECT, _('_Search Again'), gtk.STOCK_REFRESH),
+            )
+
+            if (gtk.RESPONSE_REJECT ==
+                show_message(self.__dialog,
+                             _('No IR Receivers Found'),
+                             _('Could not find any IR receiver. Is your device attached?\n\n' +
+                             'Note that some devices, such as homebrew serial port ' +
+                             'receivers must be selected manually since there is no ' +
+                             'way to detect them automatically.'),
+                             buttons=responses)):
+
+                self._on_button_auto_detect_clicked()
+
+    def _on_receiver_found(self, manager, receiver, udi, device):
+        '''Update the list of auto-detected receivers, when a new one was found.'''
+
+        if self.__receiver_chooser.append(receiver, udi, device) > 1:
+            self.__receiver_chooser.show()
+
+    def _select_chosen_receiver(self):
+        '''Choose the receiver selected in the auto-detection list.'''
+
+        # Unset any currently-chosen manufacturer/model, so that the combo
+        # box emits a signal when we set a detected manufacturer/model, even
+        # if it's the same as what was previously chosen. This ensures that
+        # the configuration file will be set again (It might have been broken
+        # in the meantime).
+        self.selected_receiver = None
+        self.selected_remote = None
+
+        self.selected_receiver = self.__receiver_chooser.selected_receiver
+
+        if self.supplied_remote:
+            self.selected_remote = self.supplied_remote
+            self.use_supplied_remote = True
+
+    def _begin_update_configuration(self):
+        '''
+        Temporarly prevent UI changes from being written back to configuration
+        files. Must be paired with _end_update_configuration() call.
+        '''
+
+        if self.__configuration_level >= 0:
+            self.__configuration_level += 1
+
+    def _end_update_configuration(self, device, *args):
+        '''
+        Allow UI changes to be written back to configuration files again.
+        Must be paired with _begin_update_configuration() call.
+        '''
+
+        if self.__configuration_level < 0:
+            return
+
+        self.__configuration_level -= 1
+
+        try:
+            service = backend.get_service()
+
+            while True:
+                try:
+                    if device:
+                        # This can throw an AccessDeniedException exception:
+                        device.update_configuration(service, *args)
+                        device = None
+
+                    elif len(args):
+                        service.ManageLircDaemon('disable')
+
+                    if 0 == self.__configuration_level:
+                        # This can throw an AccessDeniedException exception:
+                        service.ManageLircDaemon('restart')
+
+                        if self.__key_listener:
+                            self.__key_listener.start()
+
+                    break
+
+                except dbus.DBusException, ex:
+                    exception_name = ex.get_dbus_name()
+
+                    # The backend might complain
+                    # that we have not yet requested authorization:
+                    if exception_name == 'org.gnome.LircProperties.AccessDeniedException':
+                        logging.debug('Access denied, reauthenticating: %r', ex)
+
+                        #Request authorization from PolicyKit so we can try again.
+                        granted = self._unlock()
+
+                        if not granted:
+                            # _unlock() already shows a dialog.
+                            # (Though it is maybe not the right place to do that.)
+                            show_message(self.__dialog,
+                                         _('Cannot Update Configuration'),
+                                         _('The System has refused access to this feature.'))
+                            break # Stop trying.
+
+                    elif exception_name.startswith('org.gnome.LircProperties.'):
+                        logging.debug('Operation failed, aborting: %r', ex)
+
+                        show_message(self.__dialog,
+                                     _('Cannot Update Configuration'),
+                                     _('Configuration backend reported %s.') % ex.message)
+                        break # Stop trying.
+
+                    else:
+                        logging.error(ex)
+                        break # Stop trying.
+
+        except dbus.DBusException, ex:
+            logging.error(ex)
+
+    def _on_receiver_chooser_dialog_response(self, dialog, response):
+        '''Handle confirmed selections in the chooser for auto-detected receivers.'''
+
+        if gtk.RESPONSE_ACCEPT == response:
+            self._select_chosen_receiver()
+
+        dialog.hide()
+
+    def _on_button_auto_detect_clicked(self, button=None):
+        '''Handle clicks on the auto-detection button.'''
+
+        # bring user interface to initial state:
+        self._on_search_progress(None, 0, _('Searching for remote controls...'))
+
+        if not self.__receiver_chooser:
+            self.__receiver_chooser = ReceiverChooserDialog(self.__ui)
+
+        self.__receiver_chooser.reset()
+
+        # start searching for supported remote controls:
+        self.__hardware_manager.search_receivers()
+
+    def _on_auto_detect_stop_button_clicked(self, button):
+        '''Handle clicks on the auto-detection's "Cancel" button.'''
+
+        self.__hardware_manager.cancel()
+
+    def _on_custom_configuration_button_clicked(self, button):
+        '''Handle clicks on the auto-detection's "Custom Configuration" button.'''
+
+        if not self.__custom_configuration:
+            self.__custom_configuration = CustomConfiguration(self.__ui)
+
+        self.__custom_configuration.run(self.selected_receiver,
+                                        self.selected_device,
+                                        self.selected_remote)
+
+    def _on_button_close_clicked(self, button):
+        '''Handle clicks on the "Close" button.'''
+
+        self.__dialog.hide()
+
+    def _on_button_unlock_clicked(self, button):
+        '''Handle clicks on the "Unlock" button.'''
+
+        granted = self._unlock()
+
+        if granted:
+            self.__combo_receiver_vendor_list.grab_focus()
+
+    def _unlock(self):
+        '''
+        Ask PolicyKit to allow the user to use our D-BUS driven backend.
+        We must ask PolicyKit again later before actually using the backend,
+        but PolicyKit should only show the dialog the first time. Actually, use
+        __auth.is_authorized() to check if we are already authorized, because
+        ObtainAuthorization returns 0 if we are already authorized (which is
+        probably a temporary bug).
+        See http://bugs.freedesktop.org/show_bug.cgi?id=14600
+        '''
+
+        if self.__dialog == None:
+            logging.warning('_unlock() called before the dialog ' +
+                            'was instantiated, but we need an xid')
+            return False
+
+        if not self.__dialog.window:
+            logging.warning('_unlock() called before the dialog ' +
+                            'was realized, but we need an xid')
+            return False
+
+        if self.__auth.is_authorized():
+            logging.info('Authorized already. No need to obtain authorization.')
+            return True
+
+        granted = self.__auth.obtain_authorization(self.__dialog)
+
+        # Warn the user (because PolicyKit does not seem to)
+        # Note that PolicyKit can fail silently (just returning 0) when
+        # something is wrong with the backend. And it fails (!) when
+        # the user is _already_ authenticated.
+        if not granted:
+            # Improve this text
+            # (PolicyKit should maybe show this instead of failing silently,
+            # or at least something should be recommended by PolicyKit.):
+            # See http://bugs.freedesktop.org/show_bug.cgi?id=14599
+            show_message(self.__dialog,
+                         _('Could Not Unlock.'),
+                         _('The system will not allow you to access ' +
+                           'these features. Please contact your system ' +
+                           'administrator for assistance.'))
+
+        self._set_widgets_locked(not granted)
+
+        return granted
+
+    def _set_widgets_locked(self, locked):
+        '''Gray (or ungray) widgets which require PolicyKit authorization.'''
+        self.__ui.get_widget('vbox').set_sensitive(not locked)
+
+        # Gray out the Unlock button if we are now already unlocked:
+        button = self.__ui.get_widget('unlockbutton')
+        button.set_sensitive(locked)
+
+    # pylint: disable-msg=R0201
+    def _on_button_help_clicked(self, button):
+        '''Handle "Help" button clicks.'''
+
+        display_name = self.__dialog.get_screen().make_display_name()
+        args = 'yelp', 'ghelp:gnome-lirc-properties'
+
+        try:
+            subprocess.Popen(args, close_fds=True,
+                             env=dict(os.environ, DISPLAY=display_name))
+
+        except OSError, ex:
+            if errno.ENOENT == ex.errno:
+                error_message = _('Cannot display help since the GNOME Help ' +
+                                  'Browser ("yelp") cannot be found.')
+
+            else:
+                error_message = _('Cannot display help for unexpected reason: %s') % ex.strerror
+
+            show_message(self.__dialog, _('Cannot Display Help'), details=error_message)
+
+    # pylint: disable-msg=R0201
+    def _on_window_hide(self, window):
+        '''React when the dialog is hidden.'''
+
+        # Stop the main loop so that run() returns:
+        gtk.main_quit()
+
+    def _on_lirc_properties_dialog_realize(self, dialog):
+        '''Start services which need the dialog from being realized.'''
+
+        self.__key_listener.start()
+
+    def run(self):
+        '''Show the dialog and return when the window is hidden.'''
+
+        self.__dialog.connect('hide', self._on_window_hide)
+        self.__dialog.show()
+
+        gtk.main()
+
+    def __get_selected_receiver(self):
+        '''Retrieve the currently selected receiver.'''
+
+        tree_iter = self.__combo_receiver_product_list.get_active_iter()
+        receiver = None
+
+        if tree_iter:
+            receiver, = self.__combo_receiver_product_list.get_model().get(tree_iter, 1)
+
+        return receiver
+
+    def __set_selected_receiver(self, receiver):
+        '''
+        Select the specified receiver.
+
+        The receiver can be specified as Receiver object,
+        or as tuple consisting of vendor and product name.
+        Additionaly the device node can be passed.
+        '''
+
+        self._begin_update_configuration()
+
+        vendor_name, product_name, device = None, None, None
+
+        # unpack the argument(s) passed:
+        if isinstance(receiver, (tuple, list)):
+            if 3 == len(receiver):
+                vendor_name, product_name, device = receiver
+                receiver = None
+
+            elif 2 != len(receiver):
+                raise ValueError
+
+            elif isinstance(receiver[0], lirc.Receiver):
+                receiver, device = receiver
+
+            else:
+                vendor_name, product_name = receiver
+                receiver = None
+
+        elif receiver is None:
+            vendor_name, product_name = None, None
+
+        if isinstance(receiver, lirc.Receiver):
+            vendor_name, product_name = receiver.vendor, receiver.product
+
+        try:
+            # highlight the selected receiver's vendor name:
+            tree_iter = self.__receiver_vendors.find_iter(vendor_name)
+
+            if(tree_iter == None):
+                tree_iter = self.__receiver_vendors.get_iter_first()
+
+            products = None
+
+            if tree_iter:
+                filter = self.__combo_receiver_vendor_list.get_model()
+                filter_iter = filter.convert_child_iter_to_iter(tree_iter)
+                self.__combo_receiver_vendor_list.set_active_iter(filter_iter)
+                products, = self.__receiver_vendors.get(tree_iter, 1)
+
+            # highlight the selected receiver's product name:
+            tree_iter = products and products.find_iter(product_name)
+
+            if tree_iter:
+                self.__combo_receiver_product_list.set_active_iter(tree_iter)
+
+            # enter the selected receiver's device node:
+            self.selected_device = device
+
+        finally:
+            self._end_update_configuration(receiver, device)
+
+    def __get_selected_remote(self):
+        '''Retrieve the currently selected remote.'''
+
+        tree_iter = self.__combo_remote_product_list.get_active_iter()
+        remote = None
+
+        if tree_iter:
+            remote, = self.__combo_remote_product_list.get_model().get(tree_iter, 1)
+
+        return remote
+
+    def __set_selected_remote(self, remote):
+        '''
+        Select the specified remote control.
+
+        The remote can be specified as Remote object,
+        or as tuple consisting of vendor and product name.
+        '''
+
+        # analyse what got passed:
+        if isinstance(remote, lirc.Remote):
+            vendor_name = remote.vendor
+            product_name = remote.product or remote.name
+
+        elif isinstance(remote, (tuple, list)):
+            if 2 != len(remote):
+                raise ValueError
+
+            vendor_name, product_name = remote
+
+        elif remote is None:
+            vendor_name, product_name = None, None
+
+        else:
+            raise ValueError
+
+        if product_name and not vendor_name:
+            vendor_name = _('Unknown')
+
+        # select the remote vendor:
+
+        tree_iter = (
+            self.__remote_vendors.find_iter(vendor_name) or
+            self.__remote_vendors.get_iter_first())
+
+        self.__combo_remote_vendor_list.set_active_iter(tree_iter)
+        products, = self.__remote_vendors.get(tree_iter, 1)
+
+        # select the remote product:
+
+        tree_iter = products and (
+            products.find_iter(product_name) or
+            products.get_iter_first())
+
+        if tree_iter:
+            self.__combo_remote_product_list.set_active_iter(tree_iter)
+
+    def __get_selected_device(self):
+        '''Retrieve the currently selected device.'''
+        return self.__entry_device.get_text().strip()
+
+    def __set_selected_device(self, device_node):
+        '''Change the currently selected device.'''
+
+        # try to figure out device node if not specified:
+        receiver = self.selected_receiver
+
+        if receiver:
+            if device_node is None:
+                device_node = receiver.device
+            if device_node is None:
+                device_node = self.__hardware_manager.find_instance(receiver)
+
+        # try to select choosen device node from combo box:
+        tree_model = device_node and self.__combo_device.get_model()
+        tree_iter = tree_model and tree_model.get_iter_first() or None
+
+        while tree_iter is not None:
+            node, = tree_model.get(tree_iter, 1)
+
+            if node == device_node:
+                self.__combo_device.set_active_iter(tree_iter)
+                return
+
+            tree_iter = tree_model.iter_next(tree_iter)
+
+        # device node not found in combo box, fallback to modify its entry:
+        markup = (
+            receiver and not device_node and
+            _('<b>Warning:</b> Cannot find such receiver.') or '')
+
+        self.__entry_device.set_text(device_node or '')
+        self.__label_device_name.set_markup('<small>%s</small>' % markup)
+
+    def __get_supplied_remote(self):
+        '''Retrieve the supplied remot of the currently selected remote.'''
+
+        receiver = self.selected_receiver
+
+        if receiver:
+            # pylint: disable-msg=E1103
+            return receiver.find_supplied_remote(self.__remotes_db)
+
+        return None
+
+    def __set_use_supplied_remote(self, use_supplied_remote):
+        '''Chooses if the supplied remote shall be used.'''
+
+        if use_supplied_remote:
+            self.__radiobutton_supplied_remote.set_active(True)
+
+        else:
+            self.__radiobutton_other_remote.set_active(True)
+
+    def __get_use_supplied_remote(self):
+        '''Checks if the supplied remote shall be used.'''
+
+        return self.__radiobutton_supplied_remote.get_active()
+
+    selected_receiver = property(__get_selected_receiver, __set_selected_receiver)
+    selected_remote   = property(__get_selected_remote,   __set_selected_remote)
+    selected_device   = property(__get_selected_device,   __set_selected_device)
+
+    use_supplied_remote = property(__get_use_supplied_remote, __set_use_supplied_remote)
+    supplied_remote     = property(__get_supplied_remote)

Added: trunk/gnome_lirc_properties/ui/__init__.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/__init__.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,30 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+User interface related services.
+'''
+
+# publish common functions
+# pylint: disable-msg=W0401
+from gnome_lirc_properties.ui.common                  import *
+
+# publish dialog classes
+from gnome_lirc_properties.ui.CustomConfiguration     import CustomConfiguration
+from gnome_lirc_properties.ui.ProgressWindow          import ProgressWindow
+from gnome_lirc_properties.ui.ReceiverChooserDialog   import ReceiverChooserDialog
+from gnome_lirc_properties.ui.RemoteControlProperties import RemoteControlProperties

Added: trunk/gnome_lirc_properties/ui/common.py
==============================================================================
--- (empty file)
+++ trunk/gnome_lirc_properties/ui/common.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,86 @@
+# Infrared Remote Control Properties for GNOME
+# Copyright (C) 2008 Fluendo Embedded S.L. (www.fluendo.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+'''
+Common UI routines.
+'''
+
+import gtk, gtk.gdk, logging
+
+def show_message(parent, message, details=None,
+                 message_type=gtk.MESSAGE_ERROR,
+                 buttons=gtk.BUTTONS_OK):
+    '''
+    Shows a message dialog.
+    '''
+
+    stock_buttons = gtk.BUTTONS_NONE
+
+    if isinstance(buttons, gtk.ButtonsType):
+        stock_buttons = buttons
+        buttons = tuple()
+
+    dialog = gtk.MessageDialog(parent=parent, flags=gtk.DIALOG_MODAL,
+                               buttons=stock_buttons, type=message_type,
+                               message_format = message)
+
+    # Apply secondary text when supplied:
+    if details:
+        dialog.format_secondary_markup(details)
+
+    # Apply custom buttons when supplied:
+    for i, response, text in [[i] + list(b[:2]) for i, b in enumerate(buttons)]:
+        button = dialog.add_button(text, response)
+
+        if 3 == len(buttons[i]):
+            stock_id = buttons[i][2]
+            image = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
+            button.set_image(image)
+
+    if buttons:
+        dialog.set_default_response(buttons[-1][0])
+
+    # Show and run the dialog:
+    response = dialog.run()
+    dialog.destroy()
+
+    return response
+
+def thread_callback(callback):
+    '''
+    Decorate a function with code to acquire and release the big GDK lock.
+    '''
+
+    def wrapper(*args, **kwargs):
+        '''
+        Wrapper around the function to acquire and release the big GDK lock.
+        '''
+
+        logging.info('waiting for big GDK lock: %s', callback.__name__)
+        gtk.gdk.threads_enter()
+
+        try:
+            callback(*args, **kwargs)
+
+        finally:
+            logging.info('releasing big GDK lock: %s', callback.__name__)
+            gtk.gdk.threads_leave()
+
+    wrapper.callback = callback
+
+    return wrapper
+

Added: trunk/help/.gitignore
==============================================================================
--- (empty file)
+++ trunk/help/.gitignore	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+*.omf

Added: trunk/help/C/figures/auto-detect.png
==============================================================================
Binary file. No diff available.

Added: trunk/help/C/figures/custom-remote-basics.png
==============================================================================
Binary file. No diff available.

Added: trunk/help/C/figures/custom-remote-keys.png
==============================================================================
Binary file. No diff available.

Added: trunk/help/C/figures/custom-remote-model.png
==============================================================================
Binary file. No diff available.

Added: trunk/help/C/figures/main-window.png
==============================================================================
Binary file. No diff available.

Added: trunk/help/C/gnome-lirc-properties.xml
==============================================================================
--- (empty file)
+++ trunk/help/C/gnome-lirc-properties.xml	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,447 @@
+<?xml version="1.0"?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"; [
+  <!ENTITY appname          "GNOME Infrared Remote Control Properties">
+  <!ENTITY app "<application>GNOME Infrared Remote Control Properties</application>">
+  <!ENTITY appversion   "0.2">
+  <!ENTITY manrevision  "2.0">
+  <!ENTITY date "February 2008">
+]>
+<!--
+      (Do not remove this comment block.)
+  Maintained by the GNOME Documentation Project
+  http://live.gnome.org/DocumentationProject
+  Template version: 3.0 beta
+  Template last modified 2006-11-21
+-->
+<!-- =============Document Header ============================= -->
+<article id="index" lang="en">
+  <articleinfo>
+    <title>&app; Manual</title>
+    <abstract role="description">
+      <para>&app; is a tool for configuring your remote control.</para>
+    </abstract>
+
+    <copyright>
+      <year>2008</year>
+      <holder>GNOME Documentation Project</holder>
+    </copyright>
+
+    <!-- An address can be added to the publisher information. -->
+    <publisher role="maintainer">
+      <publishername>GNOME Documentation Project</publishername>
+    </publisher>
+
+    <xi:include href="legal.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
+    <!-- The file legal.xml contains link to license for the documentation,
+        and other legal stuff such as "NO WARRANTY" statement.
+        Please do not change any of this. -->
+
+    <authorgroup>
+      <author>
+        <firstname>Mathias</firstname>
+        <surname>Hasselmann</surname>
+        <affiliation>
+          <orgname>Openismus GmbH</orgname>
+          <address><email>mathias openismus com</email></address>
+        </affiliation>
+      </author>
+    </authorgroup>
+
+<!-- According to GNU FDL, revision history is mandatory if you are -->
+<!-- modifying/reusing someone else's document.  If not, you can omit it. -->
+<!-- Remember to remove the &manrevision; entity from the revision entries other
+-->
+<!-- than the current revision. -->
+<!-- The revision numbering system for GNOME manuals is as follows: -->
+<!-- * the revision number consists of two components -->
+<!-- * the first component of the revision number reflects the release version of the GNOME desktop. -->
+<!-- * the second component of the revision number is a decimal unit that is incremented with each revision of the manual. -->
+<!-- For example, if the GNOME desktop release is V2.x, the first version of the manual that -->
+<!-- is written in that desktop timeframe is V2.0, the second version of the manual is V2.1, etc. -->
+<!-- When the desktop release version changes to V3.x, the revision number of the manual changes -->
+<!-- to V3.0, and so on. -->
+    <revhistory>
+      <revision>
+        <revnumber>&appname; Manual V&manrevision;</revnumber>
+        <date>&date;</date>
+        <revdescription>
+          <para role="author">Mathias Hasselmann <email>mathias openismus com</email></para>
+          <para role="publisher">GNOME Documentation Project</para>
+        </revdescription>
+      </revision>
+    </revhistory>
+
+    <releaseinfo>This manual describes version &appversion; of &appname;</releaseinfo>
+
+    <legalnotice>
+      <title>Feedback</title>
+
+      <para>
+        To report a bug or make a suggestion regarding the &app; application or this manual,
+        follow the directions in the <ulink url="ghelp:user-guide?feedback-bugs"
+        type="help">Feedback section of the GNOME User Guide</ulink>.
+      </para>
+<!-- Translators may also add here feedback address for translations -->
+    </legalnotice>
+  </articleinfo>
+
+  <indexterm zone="index">
+    <primary>&appname;</primary>
+  </indexterm>
+  <indexterm zone="index">
+    <primary>mygnomeapp</primary>
+  </indexterm>
+
+<!-- ============= Document Body ============================= -->
+<!-- ============= Introduction ============================== -->
+<!-- Use the Introduction section to give a brief overview of what
+     the application is and what it does. -->
+  <sect1 id="gnome-lirc-properties-introduction">
+    <title>Introduction</title>
+
+    <para>
+      Use &app; to configure your LIRC powered infrared remote.
+      &app; provides the following features:
+    </para>
+
+    <itemizedlist>
+      <listitem><para>Auto-detection of infrared receivers.</para></listitem>
+      <listitem><para>Selection and customization of remote configurations.</para></listitem>
+      <listitem><para>Learning of remote control key-codes.</para></listitem>
+      <listitem><para>Sharing of customized remote configurations.</para></listitem>
+    </itemizedlist>
+
+    <note>
+      <para>Please help the community by:</para>
+
+      <itemizedlist>
+        <listitem><para>Sharing your newly created and corrected remote configuration files.</para></listitem>
+        <listitem><para>Reporting receivers supported by LIRC, but ignored by this control panel.</para></listitem>
+        <listitem><para>Reporting other issues.</para></listitem>
+        <listitem><para>Translating the program and its manual to your native language.</para></listitem>
+      </itemizedlist>
+    </note>
+  </sect1>
+
+<!-- =========== Getting Started ============================== -->
+<!-- Use the Getting Started section to describe the steps required
+     to start the application and to describe the user interface components
+     of the application. If there is other information that it is important
+     for readers to know before they start using the application, you should
+     also include this information here.
+     If the information about how to get started is very short, you can
+     include it in the Introduction and omit this section. -->
+
+  <sect1 id="gnome-lirc-properties-getting-started">
+    <title>Getting Started</title>
+
+    <sect2 id="gnome-lirc-properties-start">
+      <title>Starting &app;</title>
+      <para>You can start &app; in the following ways:</para>
+
+      <variablelist>
+        <varlistentry>
+          <term><guimenu>System</guimenu> menu</term>
+          <listitem>
+            <para>
+              Choose <menuchoice><guisubmenu>Administration</guisubmenu>
+              <guimenuitem>Infrared Remote Control</guimenuitem></menuchoice>.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>Command line</term>
+          <listitem>
+            <para>
+              To start &app; from a command line, type the following command,
+              then press <keycap>Return</keycap>:
+            </para>
+            <para><command>gnome-lirc-properties</command></para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-when-start">
+      <title>When You Start &app;</title>
+      <para>When you start &app;, the following window is displayed.</para>
+
+      <figure id="mainwindow-fig">
+        <title>&app; Start Up Window</title>
+        <screenshot>
+          <mediaobject>
+            <imageobject><imagedata fileref="figures/main-window.png" format="PNG"/></imageobject>
+            <textobject><phrase>Shows &app; main window. Contains receiver selection, remote selection and test area.</phrase></textobject>
+          </mediaobject>
+        </screenshot>
+      </figure>
+
+      <para>The &app; window contains the following elements:</para>
+
+      <variablelist>
+        <varlistentry>
+          <term>Receiver Selection.</term>
+          <listitem>
+            <para>
+              The drop-down lists in this area allow your to select brand
+              and model of your infrared receiver.
+            </para>
+
+            <para>
+              For few devices you'll have to select which physical device to use.
+              In that case the <guilabel>Device</guilabel> entry is sensitiv.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>Remote Selection.</term>
+          <listitem>
+            <para>
+              The widgets in this area allow you to select brand in model of
+              your infrared remote. In many cases it is sufficient to just
+              use the supplied remote control.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>Test Area.</term>
+          <listitem>
+            <para>
+              This area shows you the results of your configuration attempts.
+              Press the buttons of your remote to check if they are recognized
+              correctly.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </sect2>
+  </sect1>
+
+<!-- ================ Usage ================================ -->
+<!-- Use this section to describe how to use the application
+     to perform the tasks for which the application is designed.
+     If this section runs to more than a few screens in Yelp,
+     consider splitting it into several top-level sections.
+  -->
+  <sect1 id="gnome-lirc-properties-usage">
+    <title>Usage</title>
+    <para>You can use the &app; application to perform the following tasks:
+
+    <itemizedlist>
+      <listitem><para><xref linkend="gnome-lirc-properties-auto-detect"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-customize"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-new-remote"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-learn-keys"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-upload"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-download"/></para></listitem>
+    </itemizedlist>
+    </para>
+
+    <sect2 id="gnome-lirc-properties-auto-detect">
+      <title>Detect Infrared Receivers</title>
+      <para>TODO: Write this section</para>
+
+      <figure id="auto-detect-fig">
+        <title>Dialog for choosing between detected receivers</title>
+        <screenshot>
+          <mediaobject>
+            <imageobject><imagedata fileref="figures/auto-detect.png" format="PNG"/></imageobject>
+            <textobject><phrase>Shows the dialog for choosing between multiple detected receivers. Contains a list with all detected receivers, and buttons for confirming or rejecting the selection.</phrase></textobject>
+          </mediaobject>
+        </screenshot>
+      </figure>
+
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-customize">
+      <title>Customize Remote Configuration Files</title>
+      <para>TODO: Write this section</para>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-new-remote">
+      <title>Configure a new Remote Control</title>
+      <para>TODO: Write this section</para>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-learn-keys">
+      <title>Learn your Remote Control's Key Codes</title>
+      <para>TODO: Write this section</para>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-upload">
+      <title>Upload Remote Configuration Files</title>
+      <para>TODO: Write this section</para>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-download">
+      <title>Download Remote Configuration Files</title>
+      <para>TODO: Write this section</para>
+    </sect2>
+  </sect1>
+
+  <!-- ============= Customization ============================= -->
+  <!-- Use this section to describe how to customize
+       the application. -->
+
+  <sect1 id="gnome-lirc-properties-prefs">
+    <title>Preferences</title>
+
+    <para>
+      To customize your remote's configuration, activate
+      <guilabel>Use different remote control</guilabel> and press the
+      <guibutton>Custom Configuration</guibutton> button. The <guilabel>Custom
+      Configuration</guilabel> dialog contains the following tabbed sections:
+    </para>
+
+    <itemizedlist>
+      <listitem><para><xref linkend="gnome-lirc-properties-prefs-model"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-prefs-basics"/></para></listitem>
+      <listitem><para><xref linkend="gnome-lirc-properties-prefs-keys"/></para></listitem>
+    </itemizedlist>
+
+    <!-- =============== Customization Subsection ================ -->
+    <!-- Use a new section to describe different tabbed sections
+         on the Preferences dialog. -->
+
+    <sect2 id="gnome-lirc-properties-prefs-model">
+      <title>Remote Model</title>
+      <para>This section is used to describe your remote control.</para>
+
+      <figure id="custom-remote-model-fig">
+        <title><guilabel>Remote Model</guilabel> section</title>
+        <screenshot>
+          <mediaobject>
+            <imageobject><imagedata fileref="figures/custom-remote-model.png" format="PNG"/></imageobject>
+            <textobject><phrase>Shows the <guilabel>Remote Model</guilabel> section of the <guilabel>Customization Configuration</guilabel> dialog. Contains text entries for the remote's manufacturer, model and the configuration's contributor.</phrase></textobject>
+          </mediaobject>
+        </screenshot>
+      </figure>
+
+      <variablelist>
+        <varlistentry>
+          <term><guilabel>Manufacturer</guilabel></term>
+          <listitem><para>Put the official name of your remote's manufacturer here.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><guilabel>Model</guilabel></term>
+          <listitem><para>Put the official model name of your remote's here.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><guilabel>Contributor</guilabel></term>
+          <listitem><para>Put your own name here. Your work deserves acknowledgement.</para></listitem>
+        </varlistentry>
+      </variablelist>
+    </sect2>
+
+    <!-- ============= Customization Subsection ===================== -->
+    <!-- Another tabbed section on the Preferences dialog. -->
+
+    <sect2 id="gnome-lirc-properties-prefs-basics">
+      <title>Basic Configuration</title>
+      <para>
+        This section shows the basic configuration properties of your IR remote.
+        Your remote cannot be used, unless this parameters are recognized.
+        Press the <guibutton>Detect</guibutton> button to start guided
+        detection of those properties.
+      </para>
+
+      <figure id="custom-remote-basic-fig">
+        <title><guilabel>Basic Configuration</guilabel> section</title>
+        <screenshot>
+          <mediaobject>
+            <imageobject><imagedata fileref="figures/custom-remote-basics.png" format="PNG"/></imageobject>
+            <textobject><phrase>Shows the <guilabel>Basic Configuration</guilabel> section of the <guilabel>Customization Configuration</guilabel> dialog. Contains a list with detected remote properties and a button for starting detection of those properties.</phrase></textobject>
+          </mediaobject>
+        </screenshot>
+      </figure>
+    </sect2>
+
+    <sect2 id="gnome-lirc-properties-prefs-keys">
+      <title>Key Codes</title>
+      <para>
+        This section allows assignment of key-codes to well-known name.
+        Double-click a key-codes row to start learning mode. Use names
+        from the default namespace whenever possible, for maximum
+        interoperability.
+      </para>
+
+      <figure id="custom-remote-keys-fig">
+        <title><guilabel>Key Codes</guilabel> section</title>
+        <screenshot>
+          <mediaobject>
+            <imageobject><imagedata fileref="figures/custom-remote-keys.png" format="PNG"/></imageobject>
+            <textobject><phrase>Shows the <guilabel>Key Codes</guilabel> section of the <guilabel>Customization Configuration</guilabel> dialog. Contains a list with assigned keys codes and buttons for manipulating this list.</phrase></textobject>
+          </mediaobject>
+        </screenshot>
+      </figure>
+
+      <variablelist>
+        <varlistentry>
+          <term><guilabel>Add</guilabel></term>
+          <listitem><para>
+            Add another key to the configuration. Learning mode starts directly
+            after pressing this button.
+          </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><guilabel>Remote</guilabel></term>
+          <listitem><para>Deletes the currently selected key.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><guilabel>Add</guilabel></term>
+          <listitem><para>Deletes all key definitions.</para></listitem>
+        </varlistentry>
+      </variablelist>
+    </sect2>
+  </sect1>
+
+<!-- ============= Bugs ================================== -->
+<!-- This section is optional and is commented out by default.
+     You can use it to describe known bugs and limitations of the
+    program if there are any - please be frank and list all
+     problems you know of.
+
+  <sect1 id="mayapp-bugs">
+  <title>Known Bugs and Limitations</title>
+  <para> </para>
+ </sect1>
+-->
+<!-- ============= About ================================== -->
+<!-- This section contains info about the program (not docs), such as
+      author's name(s), web page, license, feedback address. This
+      section is optional: primary place for this info is "About.." box of
+      the program. However, if you do wish to include this info in the
+      manual, this is the place to put it. Alternatively, you can put this information in the title page.-->
+  <sect1 id="gnome-lirc-properties-about">
+    <title>About &app;</title>
+
+    <para>
+     &app; was written by Mathias Hasselmann (<email>mathias openismus com</email>) and Murray
+     Cumming (<email>murrayc murrayc com</email>. To find more information about &app;, please
+     visit the <ulink url="https://code.fluendo.com/remotecontrol/trac/"; type="http">project
+     page</ulink>.
+    </para>
+
+    <para>
+      To report a bug or make a suggestion regarding this application or this manual, follow
+      the directions in the <ulink url="ghelp:user-guide?feedback-bugs" type="help">Feedback
+      section of the GNOME User Guide</ulink>.
+    </para>
+
+    <para>
+      This program is distributed 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. A <ulink url="ghelp:gpl" type="help">copy of this license</ulink>
+      is included with this documentation; another can be found in the file
+      COPYING included with the source code of this program.
+     </para>
+  </sect1>
+</article>

Added: trunk/help/C/legal.xml
==============================================================================
--- (empty file)
+++ trunk/help/C/legal.xml	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,72 @@
+   <legalnotice id="legalnotice">
+         <para>
+           Permission is granted to copy, distribute and/or modify this
+           document under the terms of the GNU Free Documentation
+           License (GFDL), Version 1.1 or any later version published
+           by the Free Software Foundation with no Invariant Sections,
+           no Front-Cover Texts, and no Back-Cover Texts.  You can find
+           a copy of the GFDL at this <ulink type="help"
+           url="ghelp:fdl">link</ulink> or in the file COPYING-DOCS
+           distributed with this manual.
+          </para>
+          <para> This manual is part of a collection of GNOME manuals
+           distributed under the GFDL.  If you want to distribute this
+           manual separately from the collection, you can do so by
+           adding a copy of the license to the manual, as described in
+           section 6 of the license.
+         </para>
+
+         <para>
+           Many of the names used by companies to distinguish their
+           products and services are claimed as trademarks. Where those
+           names appear in any GNOME documentation, and the members of
+           the GNOME Documentation Project are made aware of those
+           trademarks, then the names are in capital letters or initial
+           capital letters.
+         </para>
+
+         <para>
+           DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT ARE PROVIDED
+           UNDER  THE TERMS OF THE GNU FREE DOCUMENTATION LICENSE
+           WITH THE FURTHER UNDERSTANDING THAT:
+
+           <orderedlist>
+                 <listitem>
+                   <para>DOCUMENT IS PROVIDED ON AN "AS IS" BASIS,
+                     WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+                     IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
+                     THAT THE DOCUMENT OR MODIFIED VERSION OF THE
+                     DOCUMENT IS FREE OF DEFECTS MERCHANTABLE, FIT FOR
+                     A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE
+                     RISK AS TO THE QUALITY, ACCURACY, AND PERFORMANCE
+                     OF THE DOCUMENT OR MODIFIED VERSION OF THE
+                     DOCUMENT IS WITH YOU. SHOULD ANY DOCUMENT OR
+                     MODIFIED VERSION PROVE DEFECTIVE IN ANY RESPECT,
+                     YOU (NOT THE INITIAL WRITER, AUTHOR OR ANY
+                     CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
+                     SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+                     OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
+                     LICENSE. NO USE OF ANY DOCUMENT OR MODIFIED
+                     VERSION OF THE DOCUMENT IS AUTHORIZED HEREUNDER
+                     EXCEPT UNDER THIS DISCLAIMER; AND
+                     UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL
+                     THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE),
+                     CONTRACT, OR OTHERWISE, SHALL THE AUTHOR,
+                     INITIAL WRITER, ANY CONTRIBUTOR, OR ANY
+                     DISTRIBUTOR OF THE DOCUMENT OR MODIFIED VERSION
+                     OF THE DOCUMENT, OR ANY SUPPLIER OF ANY OF SUCH
+                     PARTIES, BE LIABLE TO ANY PERSON FOR ANY
+                     DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
+                     CONSEQUENTIAL DAMAGES OF ANY CHARACTER
+                     INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+                     OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+                     MALFUNCTION, OR ANY AND ALL OTHER DAMAGES OR
+                     LOSSES ARISING OUT OF OR RELATING TO USE OF THE
+                     DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT,
+                     EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF
+                     THE POSSIBILITY OF SUCH DAMAGES.
+                   </para>
+                 </listitem>
+           </orderedlist>
+         </para>
+   </legalnotice>

Added: trunk/help/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/help/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,17 @@
+include $(top_srcdir)/gnome-doc-utils.make
+
+dist-hook: doc-dist-hook
+
+DOC_MODULE = gnome-lirc-properties
+
+DOC_ENTITIES = legal.xml
+DOC_INCLUDES =
+
+DOC_FIGURES = \
+	figures/main-window.png \
+	figures/auto-detect.png \
+	figures/custom-remote-model.png \
+	figures/custom-remote-basics.png \
+	figures/custom-remote-keys.png
+
+DOC_LINGUAS =

Added: trunk/help/gnome-lirc-properties.omf.in
==============================================================================
--- (empty file)
+++ trunk/help/gnome-lirc-properties.omf.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<omf>
+  <resource>
+    <subject category="GNOME|Settings"/>
+    <type>user's guide</type>
+    <relation seriesid="9c8f6bc4-34fb-4cf5-a81b-e89418cfd6ca"/>
+    <rights type="GNU FDL" license.version="1.1" holder="GNOME Documentation Project"/>
+  </resource>
+</omf>

Added: trunk/man/gnome-lirc-properties.1
==============================================================================
--- (empty file)
+++ trunk/man/gnome-lirc-properties.1	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,21 @@
+.TH "gnome-lirc-properties" 1
+.SH NAME
+gnome-lirc-properties \- A control panel to configure remote controls.
+.SH SYNOPSIS
+.B gnome-lirc-properties
+.SH DESCRIPTION
+This control panel updates lirc's configuration files to match your choices.
+It also allows editing, uploading and downloading of custom remote control
+configuration files.
+
+.SH AUTHORS
+Written by Murray Cumming and Mathias Hasselmann..
+
+.SH "SEE ALSO"
+The full documentation for the
+.B gnome-lirc-properties
+command is available in the GNOME help system: <ghelp:gnome-lirc-properties>.
+
+.SH "PROJECT PAGE"
+The project page is available at <https://code.fluendo.com/remotecontrol/trac/>.
+

Added: trunk/patches/0001-Use-new-instead-of-conf-as-filename-suffix.patch
==============================================================================
--- (empty file)
+++ trunk/patches/0001-Use-new-instead-of-conf-as-filename-suffix.patch	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,28 @@
+From 3e45e512719feccaa16edfd208273bade4f724e4 Mon Sep 17 00:00:00 2001
+From: Mathias Hasselmann <mathias openismus com>
+Date: Wed, 13 Feb 2008 21:16:09 +0100
+Subject: Use '.new' instead of '.conf' as filename suffix in template mode,
+ and append that the suffix to 'filename_new' instead of 'filename',
+ to prevent a buffer overrun for 'argv[optind]'.
+
+---
+ daemons/irrecord.c |    3 ++-
+ 1 files changed, 2 insertions(+), 1 deletions(-)
+
+diff --git a/daemons/irrecord.c b/daemons/irrecord.c
+index ea298e3..df03c7b 100644
+--- a/daemons/irrecord.c
++++ b/daemons/irrecord.c
+@@ -364,7 +364,8 @@ int main(int argc,char **argv)
+ 			exit(EXIT_FAILURE);
+ 		}
+ 		strcpy(filename_new,filename);
+-		strcat(filename,".conf");
++		strcat(filename_new,".new");
++		filename = filename_new;
+ 	}
+ 	fout=fopen(filename,"w");
+ 	if(fout==NULL)
+-- 
+1.5.3.7
+

Added: trunk/patches/0002-Add-resume-switch-to-irrecord.patch
==============================================================================
--- (empty file)
+++ trunk/patches/0002-Add-resume-switch-to-irrecord.patch	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,109 @@
+From d5f3f9853d87c319c33ade71811c225db11855f7 Mon Sep 17 00:00:00 2001
+From: Mathias Hasselmann <mathias openismus com>
+Date: Wed, 13 Feb 2008 21:23:43 +0100
+Subject: Add --resume switch to irrecord.
+
+This switch asks irrecord to take hardware parameters from the provided
+template file, instead of trying to interactively discover them.
+
+This change is needed for gnome-lirc-properties to allow it having a
+self-contained key-code learning mode, that's consistent with
+gnome-keybinding-properties.
+
+The 'remotes==NULL' check for LIRC_MODE_MODE2 seems to indicate, that its
+author had a similar behaviour in mind. Still I prefer having that switch,
+instead of silently switching to --resume behaviour when a templates file
+was found, for backwards compability and for being able to detect that
+feature.
+---
+ daemons/irrecord.c |   17 ++++++++++++-----
+ 1 files changed, 12 insertions(+), 5 deletions(-)
+
+diff --git a/daemons/irrecord.c b/daemons/irrecord.c
+index df03c7b..a2420a8 100644
+--- a/daemons/irrecord.c
++++ b/daemons/irrecord.c
+@@ -192,6 +192,7 @@ int main(int argc,char **argv)
+ 	lirc_t min_remaining_gap, max_remaining_gap;
+ 	int force;
+ 	int retries;
++	int resume;
+ 	struct ir_remote *remotes=NULL;
+ 	char *device=NULL;
+ #ifdef DEBUG
+@@ -200,6 +201,7 @@ int main(int argc,char **argv)
+ 
+ 	progname=argv[0];
+ 	force=0;
++	resume=0;
+ 	hw_choose_driver(NULL);
+ 	while(1)
+ 	{
+@@ -211,6 +213,7 @@ int main(int argc,char **argv)
+ 			{"device",required_argument,NULL,'d'},
+ 			{"driver",required_argument,NULL,'H'},
+ 			{"force",no_argument,NULL,'f'},
++			{"resume",no_argument,NULL,'r'},
+ #ifdef DEBUG
+ 			{"pre",no_argument,NULL,'p'},
+ 			{"post",no_argument,NULL,'P'},
+@@ -221,9 +224,9 @@ int main(int argc,char **argv)
+ 			{0, 0, 0, 0}
+ 		};
+ #ifdef DEBUG
+-		c = getopt_long(argc,argv,"hvd:H:fpPtiT",long_options,NULL);
++		c = getopt_long(argc,argv,"hvd:H:frpPtiT",long_options,NULL);
+ #else
+-		c = getopt_long(argc,argv,"hvd:H:f",long_options,NULL);
++		c = getopt_long(argc,argv,"hvd:H:fr",long_options,NULL);
+ #endif
+ 		if(c==-1)
+ 			break;
+@@ -234,6 +237,7 @@ int main(int argc,char **argv)
+ 			printf("\t -h --help\t\tdisplay this message\n");
+ 			printf("\t -v --version\t\tdisplay version\n");
+ 			printf("\t -f --force\t\tforce raw mode\n");
++			printf("\t -r --resume\t\tcontinue recording\n");
+ 			printf("\t -H --driver=driver\tuse given driver\n");
+ 			printf("\t -d --device=device\tread from given device\n");
+ 			exit(EXIT_SUCCESS);
+@@ -254,6 +258,9 @@ int main(int argc,char **argv)
+ 		case 'f':
+ 			force=1;
+ 			break;
++		case 'r':
++			resume=1;
++			break;
+ #ifdef DEBUG
+ 		case 'p':
+ 			get_pre=1;
+@@ -460,7 +467,7 @@ int main(int argc,char **argv)
+ 	switch(hw.rec_mode)
+ 	{
+ 	case LIRC_MODE_MODE2:
+-		if(remotes==NULL && !get_lengths(&remote,force))
++		if((!remotes || !resume) && !get_lengths(&remote,force))
+ 		{
+ 			if(remote.gap==0)
+ 			{
+@@ -494,7 +501,7 @@ int main(int argc,char **argv)
+ 	case LIRC_MODE_LIRCCODE:
+ 		if(hw.rec_mode==LIRC_MODE_CODE) remote.bits=CHAR_BIT;
+ 		else remote.bits=hw.code_length;
+-		if(!get_gap_length(&remote))
++		if((!remotes || !resume) && !get_gap_length(&remote))
+ 		{
+ 			fprintf(stderr,"%s: gap not found,"
+ 				" can't continue\n",progname);
+@@ -767,7 +774,7 @@ int main(int argc,char **argv)
+ 		exit(EXIT_FAILURE);
+ 	}
+ 	
+-	if(!has_toggle_bit_mask(remotes))
++	if((!remotes || !resume) && !has_toggle_bit_mask(remotes))
+ 	{
+ 		get_toggle_bit_mask(remotes);
+ 	}
+-- 
+1.5.3.7
+

Added: trunk/po/.gitignore
==============================================================================
--- (empty file)
+++ trunk/po/.gitignore	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,6 @@
+.intltool-merge-cache
+Makefile
+Makefile.in
+Makefile.in.in
+stamp-it
+POTFILES

Added: trunk/po/POTFILES.in
==============================================================================
--- (empty file)
+++ trunk/po/POTFILES.in	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,11 @@
+data/gnome-lirc-properties.desktop.in
+data/gnome-lirc-properties.glade
+gnome_lirc_properties/__init__.py
+gnome_lirc_properties/backend.py
+gnome_lirc_properties/hardware.py
+gnome_lirc_properties/lirc.py
+gnome_lirc_properties/model.py
+gnome_lirc_properties/net/services.py
+gnome_lirc_properties/ui/CustomConfiguration.py
+gnome_lirc_properties/ui/ProgressWindow.py
+gnome_lirc_properties/ui/RemoteControlProperties.py

Added: trunk/po/POTFILES.skip
==============================================================================
--- (empty file)
+++ trunk/po/POTFILES.skip	Sat Apr 19 09:45:07 2008
@@ -0,0 +1 @@
+data/gnome-lirc-properties.desktop.in.in

Added: trunk/pylint/custom-checker.py
==============================================================================
--- (empty file)
+++ trunk/pylint/custom-checker.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,32 @@
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+
+class MyChecker(BaseChecker):
+    __implements__ = IASTNGChecker
+
+    name = 'custom'
+    msgs, options = {}, ()
+    priority = -1 # execute before others
+
+    def visit_function(self, node):
+        widget_list_nodes = (
+            '__lookup_widgets' == node.name and
+            node.frame().locals.get('widget_list') or [])
+
+        if widget_list_nodes:
+            self.__register_widget_attributes(node.parent.frame(),
+                                              widget_list_nodes)
+
+    def __register_widget_attributes(self, frame, nodes):
+        for local_node in nodes:
+            if isinstance(local_node, astng.AssName):
+                for expr in local_node.parent.expr.nodes:
+                    if isinstance(expr, astng.Const):
+                        value = astng.Name('widget:%s' % expr.value)
+                        frame.set_local('__%s' % expr.value, value)
+
+def register(linter):
+    '''auto register this checker'''
+    linter.register_checker(MyChecker(linter))

Added: trunk/pylintrc
==============================================================================
--- (empty file)
+++ trunk/pylintrc	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,18 @@
+[MESSAGES CONTROL]
+disable-msg=I0011,W0141,W0142,W0511,R0902,R0903,R0923
+
+[REPORTS]
+include-ids=yes
+
+[FORMAT]
+max-line-length=100
+
+[BASIC]
+good-names=a,b,i,j,k,p,q,ex,fd
+
+[DESIGN]
+min-public-methods=0
+max-public-methods=100
+
+[MASTER]
+load-plugins=custom-checker

Added: trunk/test/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/test/Makefile.am	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,12 @@
+EXTRA_DIST = \
+	example.lircrc \
+	test-backend-service.py \
+	test-lirc-client.c \
+	test-lirc-key-listener.py \
+	test-policykit-is-authorized.py \
+	test-policykit-obtain-authorization.py \
+	test-policykit.py \
+	test-pylirc.py \
+	test-run-backend-service.sh \
+	test-udp-receiver.py \
+	test-uploader.py

Added: trunk/test/example.lircrc
==============================================================================
--- (empty file)
+++ trunk/test/example.lircrc	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,134 @@
+
+begin
+    prog = openismus-lirc-test
+    button = POWER
+    config = close_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = UP
+    config = move_up_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = DOWN
+    config = move_down_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = LEFT
+    config = move_left_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = RIGHT
+    config = move_right_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = OK
+    config = activate_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = MENU
+    config = toggle_menu_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = PLAY
+    config = toggle_play_pause_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = PAUSE
+    config = pause_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = STOP
+    config = stop_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = >>
+    config = seek_forward_key
+repeat = 1
+delay = 4
+
+end
+begin
+    prog = openismus-lirc-test
+    button = <<
+    config = seek_backward_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = >>|
+    config = next_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = |<<
+    config = previous_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = RED
+    config = toggle_fullscreen_key
+repeat = 1
+delay = 4
+end
+
+begin
+    prog = openismus-lirc-test
+    button = MUTE
+    config = toggle_mute_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = VOL_UP
+    config = increment_volume_key
+repeat = 1
+delay = 4
+end
+begin
+    prog = openismus-lirc-test
+    button = VOL_DOWN
+    config = decrement_volume_key
+repeat = 1
+delay = 4
+end

Added: trunk/test/test-backend-service.py
==============================================================================
--- (empty file)
+++ trunk/test/test-backend-service.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+policy_kit_mechanism = bus.get_object('org.gnome.LircProperties.Mechanism', '/')
+#print policy_kit_mechanism.Introspect()
+
+if(policy_kit_mechanism == None):
+    print("Error: Could not get our PolicyKit mechanism.\n")
+
+try:
+    result = policy_kit_mechanism.WriteLircdConfFile("yadda yadda test contents")
+    print "Called. result=", result
+except dbus.exceptions.DBusException, e:
+    print "exception: ", e
+except Exception, e:
+    print "other exception: ", e

Added: trunk/test/test-lirc-client.c
==============================================================================
--- (empty file)
+++ trunk/test/test-lirc-client.c	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,72 @@
+/* This test code is based on irexec.c in lirc.
+ * Build with 
+ * gcc test_lirc_client.c -llirc_client
+ * 
+ * This depends on 
+ * - A correct /etc/lirc/lircd.conf, which maps the key names to the key codes supplied by your IR remote.
+ * - A running lircd, with the corred device specified. For instance: â/usr/sbin/lircd âdevice=/dev/lirc0â.
+ * - An example.lircrc file, which maps the key names to "command" names that an application would understand.
+ * On Ubuntu/Debian, dpkg-reconfigure lirc can do this for common remotes.
+ *
+ * Murray Cumming, Openismus GmbH 
+ */
+
+#include <lirc/lirc_client.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+char *progname;
+
+int main(int argc, char *argv[])
+{
+	struct lirc_config *config;
+        const char *lirc_config = NULL;
+
+	progname=argv[0];
+	if(argc>2)
+	{
+		fprintf(stderr,"Usage: %s <config file>\n",progname);
+		exit(EXIT_FAILURE);
+	}
+
+	if(lirc_init("openismus-lirc-test",1)==-1)
+          exit(EXIT_FAILURE);
+
+        printf("lirc_init(): succeeded.\n");
+
+	lirc_config = (argc==2 ? argv[1]:"./example.lircrc");
+	if(lirc_readconfig((char*)lirc_config, &config,NULL)==0)
+	{
+                printf("lirc_readconfig(): succeeded.\n");
+
+		char *code;
+		char *c;
+		int ret;
+
+		while(lirc_nextcode(&code)==0)
+		{
+                        printf("lirc_nextcode(): succeeded.\n");
+			if(code==NULL) continue;
+			while((ret=lirc_code2char(config,code,&c))==0 &&
+			      c!=NULL)
+			{
+                                printf("lirc_code2char(): succeeded.\n");
+				printf("Command received: \"%s\"\n", c);
+				
+			}
+			free(code);
+			if(ret==-1) break;
+		}
+		lirc_freeconfig(config);
+	}
+
+	lirc_deinit();
+	exit(EXIT_SUCCESS);
+}
+
+

Added: trunk/test/test-lirc-key-listener.py
==============================================================================
--- (empty file)
+++ trunk/test/test-lirc-key-listener.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+import os.path, sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+from gnome_lirc_properties import lirc
+from gobject               import MainLoop
+
+# Called for each lirc command received:
+def on_key_pressed(command, remote, repeat, name, code):
+   print 'command received: %s (%s)' % (name, code)
+
+print '''\
+Press keys on the remote control.
+If they are recognised then they will be shown here.
+'''
+
+# Create the listener and tell it to call our
+# callback for each command that it receives:
+listener = lirc.KeyListener()
+listener.connect('key-pressed', on_key_pressed)
+listener.start()
+
+# Run the mainloop so that the idle handler inside LircKeysListener can work:
+MainLoop().run()

Added: trunk/test/test-policykit-is-authorized.py
==============================================================================
--- (empty file)
+++ trunk/test/test-policykit-is-authorized.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import dbus
+import os
+
+bus = dbus.SystemBus()
+policy_kit = bus.get_object('org.freedesktop.PolicyKit', '/')
+#print policy_kit_mechanism.Introspect()
+
+if(policy_kit == None):
+    print("Error: Could not get the PolicyKit interface.\n")
+
+action_id = "org.gnome.clockapplet.mechanism.settimezone"
+
+result = "";
+
+# Check whether the process is authorized:
+try:
+    result = policy_kit.IsProcessAuthorized(action_id, (dbus.UInt32)(os.getpid()), False)
+except dbus.exceptions.DBusException, e:
+    print "exception: ", e
+except Exception, e:
+    print "other exception: ", e
+
+print "IsProcessAuthorized() result=", result
+print "IsProcessAuthorized() authorized=", (result == "yes")
+
+# Check whether the dbus session is authorized:
+# Only works in a dbus service, so we have a sender dbus session name:
+#try:
+#    result = policy_kit.IsSystemBusNameAuthorized(action_id, "fake-sender-dbus-session-name", False)
+#except dbus.exceptions.DBusException, e:
+#    print "exception: ", e
+#except Exception, e:
+#    print "other exception: ", e
+

Added: trunk/test/test-policykit-obtain-authorization.py
==============================================================================
--- (empty file)
+++ trunk/test/test-policykit-obtain-authorization.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+import os.path, sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+from gnome_lirc_properties import policykit_obtain_authorization
+
+import pygtk
+pygtk.require('2.0')
+
+import gtk
+
+class TestWindow:
+    def on_button_clicked(self, widget, data=None):
+        print "Calling ObtainAuthorization..."
+        helper = policykit_obtain_authorization.PolicyKitObtainAuthorization()
+        helper.obtain_authorization(None)
+        print "...Finished."
+
+    def on_destroy(self, widget, data=None):
+        gtk.main_quit()
+
+    def show(self):
+        self.window.show()
+
+    def __init__(self):
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        self.window.connect("destroy", self.on_destroy)
+
+        self.button = gtk.Button("Obtain Authorization")
+        self.button.connect("clicked", self.on_button_clicked, None)
+        self.window.add(self.button)
+        self.button.show()
+
+window = TestWindow()
+window.show()
+gtk.main()
+
+

Added: trunk/test/test-policykit.py
==============================================================================
--- (empty file)
+++ trunk/test/test-policykit.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+import os.path, sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+import dbus
+import os
+
+class TestWindow:
+    def on_button_clicked(self, widget, data=None):
+
+        #Call the D-Bus method to request PolicyKit authorization:
+
+        session_bus = dbus.SessionBus()
+        policykit = session_bus.get_object('org.freedesktop.PolicyKit.AuthenticationAgent', '/', "org.gnome.PolicyKit.AuthorizationManager.SingleInstance")
+        if(policykit == None):
+           print("Error: Could not get PolicyKit D-Bus Interface\n")
+
+        gdkwindow = self.window.window
+        xid = gdkwindow.xid
+
+        print "Calling ObtainAuthorization..."
+
+        #This complains that no ObtainAuthorization(ssi) exists: 
+        #granted = policykit.ObtainAuthorization("test_action_id", xid, os.getpid())
+
+        action_id = "org.gnome.clockapplet.mechanism.settimezone"
+
+        granted = policykit.ObtainAuthorization(action_id, (dbus.UInt32)(xid), (dbus.UInt32)(os.getpid()))
+        print "...Finished."
+
+        print "granted=", granted
+
+    def on_destroy(self, widget, data=None):
+        gtk.main_quit()
+
+    def show(self):
+       self.window.show()
+
+    def __init__(self):
+
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        self.window.connect("destroy", self.on_destroy)
+
+        self.button = gtk.Button("Obtain Authorization")
+        self.button.connect("clicked", self.on_button_clicked, None)
+        self.window.add(self.button)
+        self.button.show()
+
+window = TestWindow()
+window.show()
+gtk.main()
+
+

Added: trunk/test/test-pylirc.py
==============================================================================
--- (empty file)
+++ trunk/test/test-pylirc.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# This test code is based on the pylirc "simple sample" 
+# 
+# This depends on 
+# - A correct /etc/lirc/lircd.conf, which maps the key names to the key codes supplied by your IR remote.
+# - A running lircd, with the correct device specified. For instance: "/usr/sbin/lircd --device=/dev/lirc0".
+# - An example.lircrc file, which maps the key names to "command" names that an application would understand.
+# On Ubuntu/Debian, dpkg-reconfigure lirc can do this for common remotes.
+#
+# Murray Cumming, Openismus GmbH 
+#/
+
+import os.path, pylirc, time
+
+blocking = 0;
+
+example = os.path.join(os.path.dirname(__file__), 'example.lircrc')
+
+if pylirc.init('openismus-lirc-test', example):
+   code = {"config" : ""}
+
+   while code["config"] != "quit":
+      # Very intuitive indeed
+      if not blocking:
+         print "."
+
+         # Delay...
+         time.sleep(1)
+
+      # Read next code
+      s = pylirc.nextcode(1)
+
+      # Loop as long as there are more on the queue
+      # (dont want to wait a second if the user pressed many buttons...)
+      while s:
+         # Print all the configs...
+         for code in s:
+            print "Command: %s, Repeat: %d" % (code["config"], code["repeat"])
+
+            if(code["config"] == "blocking"):
+               blocking = 1
+               pylirc.blocking(1)
+
+            elif(code["config"] == "nonblocking"):
+               blocking = 0
+               pylirc.blocking(0)
+
+         # Read next code?
+         if not blocking:
+            s = pylirc.nextcode(1)
+
+         else:
+            s = []
+
+   # Clean up lirc
+   pylirc.exit()
+

Added: trunk/test/test-run-backend-service.sh
==============================================================================
--- (empty file)
+++ trunk/test/test-run-backend-service.sh	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+dbus-send \
+    --system --print-reply \
+    --dest=org.gnome.LircProperties.Mechanism / \
+    org.freedesktop.DBus.Introspectable.Introspect

Added: trunk/test/test-udp-receiver.py
==============================================================================
--- (empty file)
+++ trunk/test/test-udp-receiver.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+from os import environ
+from socket import htons, socket, error, AF_INET, SOCK_DGRAM
+from time import sleep
+
+def encode(n):
+    high, l = True, 0
+    chunk = []
+
+    for i in range(12):
+        if n & (1 << i):
+            if not high:
+                chunk.append(l)
+                high = True
+                l = 0
+
+        else:
+            if high:
+                chunk.append(l)
+                high = False
+                l = 0
+
+        l += 1
+
+    return chunk
+
+def bars(chunk):
+    result = ''
+
+    for i, l in enumerate(chunk):
+        result += (i & 1 and '-' or '#') * l
+
+    return result
+
+addr = environ.get('HOST', '127.0.0.1'), (8765)
+sock = socket(AF_INET, SOCK_DGRAM)
+repeat = int(environ.get('REPEAT'))
+
+for key in range(160):
+    if repeat:
+        key = repeat
+
+    key  = key * 7 + 64
+    code = encode(key)
+
+    data = [255, 255, 64, 128]
+
+    for i, l in enumerate(code):
+        l *= 2000
+
+        lo, hi = (l & 255), (l >> 8) & 255
+
+        if i % 2:
+            hi |= 128
+
+        data.extend((lo, hi,))
+
+    data.extend((64, 128, 255, 255,))
+
+    print key, bars(code), data
+
+    data = ''.join(map(chr, data))
+    sock.sendto(data, 0, addr)
+
+    sleep(0.5)

Added: trunk/test/test-uploader.py
==============================================================================
--- (empty file)
+++ trunk/test/test-uploader.py	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+import os.path, sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+from gnome_lirc_properties.upload import HttpUploader
+
+#A site with a known working file upload feature: 
+LOGIN_URL='http://www.glom.org/wiki/index.php?title=Special:Userlogin&action=submitlogin&type=login&wpLoginattempt=Log+in'
+UPLOAD_URL='http://www.glom.org/wiki/index.php?title=Special:Upload&wpUpload=Upload+file'
+
+uploader = HttpUploader(UPLOAD_URL, LOGIN_URL, "Murrayc", "luftballons")
+
+#The name should start with a capital letter, and have no spaces.
+uploader.upload_file("/etc/lirc/lircd.conf", "Test_lircd.conf")

Added: trunk/web/README
==============================================================================
--- (empty file)
+++ trunk/web/README	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,82 @@
+This folder contains a tiny service for sharing lirc configuration files.
+Nothing fancy yet, just upload and download from the application.
+No browsing, rating, moderation and such yet.
+
+== Installation ==
+
+For using the script a WSGI enabled[1][2] web server is needed. When using the
+Apache web server modwsgi[3][4] is the module of choice. It is easy to setup
+and offers the best performance.
+
+Alternatively the CGI wrapper or the standalone HTTP server of wsgiref[5]
+could be used. There also are several WSGI gateways for mod_python, its FAQ
+recommends ModPythonGateway[6][7].
+
+=== Apache and modwsgi ===
+
+ * copy "service.wsgi" to some folder of your choice, e.g. "/var/www/lircdb"
+ * create the file "/etc/apache2/conf.d/lircdb.conf" with this content:
+
+        WSGIScriptAlias /lircdb /var/www/lircdb/service.wsgi
+
+ * create the folder "/var/www/lircdb/archive/incoming"
+ * give the web server write access to the folders "/var/www/lircdb/archive"
+   and "/var/www/lircdb/archive/incoming":
+
+        # chown www-data:www-data /var/www/lircdb/{archive,archive/incoming}
+
+== Mode of Operation ==
+
+The script provides the following request handlers.
+
+=== Root Handler (GET /) ===
+
+This request hander delivers a static HTML form to allow manual uploads of
+configuration files.
+
+=== Download Handler (GET /remotes.tar.gz) ===
+
+This handlers provides a tarball with the latest remote configuration files for
+download. The tarball stored at "/var/www/lircdb/archive/remotes.tar.gz", and
+it is generated automatically by the request handler, if it doesn't exist yet.
+Advisory locking is used to avoid race conditions when generating that tarball.
+The tarball is deleted each time a new configuration file is uploaded.
+
+The request handler considers the "If-Modified-Since" header to reduce
+bandwidth usage.
+
+Also a custom "X-Checksum-Sha1" header containing the tarball's SHA1 hex digest
+is sent, to avoid race conditions caused by uploads happening during download
+of the tarball and download of a separate checksum file.
+
+=== Upload Handler (POST /upload/) ===
+
+This POST handler accepts data from multipart encoded HTML forms, and stores
+the uploaded file in the "incoming" folder. The configuration file's checksum
+is used as local filename: "archive/incoming/%(checksum)s.conf". The post
+handler expects the following request parameters:
+
+ * config: The contents of the configuration file.
+ * digest: The SHA1 hex digest of the configuration file.
+ * locale: Current locale of the uploading application.
+           This allows the script to provide localized status messages.
+
+The request handler implements the POST/REDIRECT/GET pattern to avoid
+duplicates. The HTTP client is redirected to "/upload/success" on
+successful uploads.
+
+=== Upload Success Handler (GET /upload/success) ===
+
+This request handler displays a static success message
+after successful uploads.
+
+== References ==
+
+1: http://www.wsgi.org/
+2: http://www.python.org/dev/peps/pep-0333/
+3: http://www.modwsgi.org/
+4: http://packages.ubuntu.com/hardy/libapache2-mod-wsgi
+5: http://docs.python.org/lib/module-wsgiref.handlers.html
+6: http://www.modpython.org/FAQ/faqw.py?req=show&file=faq03.029.htp
+7: http://projects.amor.org/misc/wiki/ModPythonGateway
+

Added: trunk/web/service.wsgi
==============================================================================
--- (empty file)
+++ trunk/web/service.wsgi	Sat Apr 19 09:45:07 2008
@@ -0,0 +1,458 @@
+# WSGI script for hosting a database of infrared remote configurations
+# Copyright (C) 2008 Openismus GmbH (www.openismus.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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# Authors:
+#   Mathias Hasselmann <mathias openismus com>
+'''
+WSGI script for hosting a database of infrared remote configurations
+'''
+
+import cgi, fcntl, httplib, os, rfc822, tarfile, time
+
+from errno   import ENOENT
+from gettext import gettext as _
+from locale  import LC_ALL, setlocale
+from sha     import sha as SHA1
+
+class Context(dict):
+    '''A WSGI request context.'''
+
+    def __init__(self, environ):
+        super(Context, self).__init__(environ)
+
+    content_type   = property(lambda self: self['CONTENT_TYPE'])
+    request_method = property(lambda self: self['REQUEST_METHOD'].upper())
+    request_uri    = property(lambda self: self['REQUEST_URI'])
+    path_info      = property(lambda self: self['PATH_INFO'])
+
+    errors = property(lambda self: self['wsgi.errors'])
+    input  = property(lambda self: self['wsgi.input'])
+
+class Response(dict):
+    '''Wrapper arround the various parts of a WSGI response.'''
+
+    def __init__(self, status=httplib.OK,
+                 content_type='text/html',
+                 content=''):
+
+        super(Response, self).__init__()
+
+        self['Content-Type'] = content_type
+        self.__output = [content]
+        self.__status = status
+
+    def __get_status(self):
+        '''Query response status as tuple of status code and message.'''
+        return self.__status, __responses__.get(self.__status, '')
+
+    def __set_status(self, status):
+        '''Update response status (code).'''
+        self.__status = int(status)
+
+    def __get_output(self):
+        '''Access the list of response chunks.'''
+        return self.__output
+
+    status = property(fget=__get_status, fset=__set_status)
+    output = property(fget=__get_output)
+
+def find_request_handler(request_method, path_info, default=None):
+    '''Finds the request handler for the current request.'''
+
+    handler_id = '%s:%s' % (request_method, path_info)
+    return __request_handlers__.get(handler_id, default)
+
+def find_locale(locale, localedir='/usr/lib/locale'):
+    '''Finds the name of the matching locallly installed locale.'''
+
+    for name in '%s.utf8' % locale, locale, locale[:2]:
+        ident = os.path.join(localedir, name, 'LC_IDENTIFICATION')
+
+        if os.path.isfile(ident):
+            return name
+
+    return None
+
+def html_page(context, status, title=None, message=None, *args):
+    '''Creates a response that contains a HTML page.'''
+
+    template = '''\
+<html>
+ <head>
+  <title>%(title)s</title>
+ </head>
+
+ <body>
+  <h1>%(title)s</h1>
+  <p>%(message)s</p>
+  <p>%(signature)s</p>
+ </body>
+</htm>'''
+
+    if not title:
+        title = __responses__[status]
+    if not message:
+        message = _('Request failed.')
+    if args:
+        message %= args
+
+    return Response(
+        status=status, content=template % dict(vars(),
+        signature=context.get('SERVER_SIGNATURE', ''),
+    ))
+
+def redirect(context, path, status=httplib.SEE_OTHER):
+    '''Creates a response that redirects to another page.'''
+
+    response = html_page(context, status, None,
+                         _('See: <a href="%(path)s">%(path)s</a>.') %
+                         dict(path=cgi.escape(path)))
+
+    response['Location'] = path
+
+    return response
+
+def not_found(context):
+    '''Creates a response that handles missing pages.'''
+
+    uri = context.request_uri
+
+    if not uri.endswith('/'):
+        fallback = find_request_handler(context.request_method,
+                                        context.path_info + '/')
+
+        if fallback:
+            return redirect(context, uri + '/')
+
+    return html_page(context, httplib.NOT_FOUND,
+                     _('Resource Not Found'),
+                     _('Cannot find this resource: <b>%s</b>.'),
+                     cgi.escape(uri))
+
+def show_upload_form(context):
+    '''Shows the HTML form for uploading LIRC configuration files.'''
+
+    template = '''\
+<html>
+ <head>
+  <title>%(title)s</title>
+ </head>
+
+ <body>
+  <h1>%(title)s</h1>
+
+  <form action="upload/" method="post" enctype="multipart/form-data">
+   <table>
+    <tr>
+     <td>Configuration File:</td>
+     <td><input name="config" type="file" size="40" value="/etc/lirc/lircd.conf"/></td>
+    </tr>
+
+    <tr>
+     <td>SHA1 Digest:</td>
+     <td><input name="digest" type="text" size="40" maxlength="40" /></td>
+    </tr>
+
+    <tr>
+     <td>Language:</td>
+     <td>
+      <select name="locale">
+       <option value="en_US">English</option>
+       <option value="de_DE">German</option>
+      </select>
+    </tr>
+
+    <tr>
+     <td colspan="2" align="right">
+      <button>Upload</button>
+     </td>
+    </tr>
+   </table>
+  </form>
+
+  <p>Download <a href="%(dburi)s">current archive</a>.</p>
+  <p>%(signature)s</p>
+ </body>
+</html>'''
+
+    return Response(content=template % dict(
+        signature=context.get('SERVER_SIGNATURE', ''),
+        title='Upload LIRC Remote Control Configuration',
+        dburi='remotes.tar.gz',
+    ))
+
+def process_upload(context):
+    '''Processes an uploaded LIRC configuration file.'''
+
+    # pylint: disable-msg=R0911
+
+    form = cgi.FieldStorage(fp=context.input, environ=context)
+
+    digest, config, locale = [
+        form.has_key(key) and form[key]
+        for key in ('digest', 'config', 'locale')]
+
+    locale = locale is not False and find_locale(locale.value)
+
+    if locale:
+        setlocale(LC_ALL, locale)
+
+    # validate request body:
+
+    if form.type != 'multipart/form-data':
+        return html_page(context, httplib.BAD_REQUEST, None,
+                         _('Request has unexpected content type.'))
+
+    if form.type != digest is False or config is False or locale is False:
+        return html_page(context, httplib.BAD_REQUEST, None,
+                         _('Some fields are missing in this request.'))
+
+    if digest.value != SHA1(config.value).hexdigest():
+        return html_page(context, httplib.BAD_REQUEST, None,
+                         _('Checksum doesn\'t match the uploaded content.'))
+
+    # process request body:
+
+    workdir  = os.path.dirname(__file__)
+    archive  = os.path.join(workdir, 'archive', 'remotes.tar.gz')
+    filename = os.path.join(workdir, 'archive', 'incoming', '%s.conf' % digest.value)
+
+    try:
+        # force rebuild by removing the obsolete archive:
+        os.unlink(archive)
+
+    except OSError, ex:
+        if ENOENT != ex.errno:
+            return html_page(context, httplib.INTERNAL_SERVER_ERROR, None,
+                             _('Cannot remove obsolete remotes archive: %s.'),
+                             ex.strerror)
+
+    # store the uploaded configuration file in the incoming folder:
+    try:
+        storage = open(filename, 'wb')
+
+    except IOError, ex:
+        return html_page(context, httplib.INTERNAL_SERVER_ERROR, None,
+                         _('Cannot store configuration file: %s.'),
+                         ex.strerror)
+
+    try:
+        fcntl.lockf(storage, fcntl.LOCK_EX)
+
+    except IOError, ex:
+        return html_page(context, httplib.INTERNAL_SERVER_ERROR, None,
+                         _('Cannot get exclusive file access: %s.'),
+                         ex.strerror)
+
+    storage.write(config.file.read())
+    storage.close()
+
+    # use POST/REDIRECT/GET pattern to avoid duplicate uploads:
+    return redirect(context, context.request_uri + 'success')
+
+def show_upload_success(context):
+    '''Shows success message after upload'''
+
+    return html_page(context, httplib.OK, _('Upload Succeeded'),
+                     _('Upload of your configuration file succeeded. '
+                       'Thanks alot for contributing.'))
+
+def send_archive(context, filename, must_exist=False):
+    '''Sends the specified tarball.'''
+
+    try:
+        # Checks last-modified time, when requested:
+        reference_time = context.get('HTTP_IF_MODIFIED_SINCE')
+
+        if reference_time:
+            reference_time = rfc822.parsedate(reference_time)
+            reference_time = time.mktime(reference_time)
+
+            if reference_time >= os.path.getmtime(filename):
+                return Response(httplib.NOT_MODIFIED)
+
+        # Deliver the file with checksum and last-modified header:
+        response = Response(content_type='application/x-gzip',
+                            content=open(filename, 'rb').read())
+
+        digest = SHA1(response.output[0]).hexdigest()
+        timestamp = time.ctime(os.path.getmtime(filename))
+
+        response['X-Checksum-Sha1'] = digest
+        response['Last-Modified'] = timestamp
+
+        return response
+
+    except (IOError, OSError), ex:
+        if must_exist or ENOENT != ex.errno:
+            return html_page(context,
+                             httplib.INTERNAL_SERVER_ERROR, None,
+                             _('Cannot read remotes archive: %s.'),
+                             ex.strerror)
+
+        return None
+
+def send_remotes_archive(context):
+    '''Sends the archive with uploaded LIRC configuration files.'''
+
+    archive_root  = os.path.join(os.path.dirname(__file__), 'archive')
+    archive_name = os.path.join(archive_root, 'remotes.tar.gz')
+
+    response = send_archive(context, archive_name)
+
+    if not response:
+        try:
+            archive = tarfile.open(archive_name, 'w:gz')
+
+        except IOError, ex:
+            return html_page(context, httplib.INTERNAL_SERVER_ERROR, None,
+                             _('Cannot create remotes archive: %s.'),
+                             ex.strerror)
+
+        try:
+            # pylint: disable-msg=W0612
+            for path, subdirs, files in os.walk(archive_root):
+                # drop folders with SCM meta-information:
+                subdirs[:] = [name for name in subdirs if
+                              not name.startswith('.')
+                              and name != 'CVS']
+
+                # drop archive_root from current folder name:
+                dirname = path[len(archive_root):]
+
+                # scan files in current folder:
+                for name in files:
+                    if name.startswith('.'):
+                        continue
+
+                    filename = os.path.join(path, name)
+                    arcname = os.path.join(dirname, name)
+
+                    if filename == archive_name:
+                        continue
+
+                    info = archive.gettarinfo(filename, arcname)
+                    archive.addfile(info, open(filename))
+
+            # Trigger finalization of the tarball instance,
+            # since Python 2.4 doesn't create its file otherwise:
+            archive.close()
+            del archive
+
+            response = send_archive(context, archive_name, must_exist=True)
+
+        except:
+            try:
+                # Try to remove artifacts on error:
+                os.unlink(archive.name)
+
+            except:
+                pass
+
+            raise
+
+    return response
+
+def application(environ, start_response):
+    '''The WSGI entry point of this service.'''
+
+    context, response = Context(environ), None
+
+    try:
+        handler = find_request_handler(context.request_method,
+                                       context.path_info,
+                                       not_found)
+        response = handler(context)
+
+    except:
+        import traceback
+
+        response = Response(content_type='text/plain',
+                            content=traceback.format_exc(),
+                            status=httplib.INTERNAL_SERVER_ERROR)
+
+        traceback.print_exc(context.errors)
+
+    if (not response.has_key('Content-Length') and
+        response.get('Transfer-Encoding', '').lower() != 'chunked'):
+        response['Content-Length'] = str(len(''.join(response.output)))
+
+    start_response('%d %s' % response.status, response.items())
+
+    return response.output
+
+# Map status codes to official W3C names (from httplib/2.5):
+__responses__ = {
+    100: 'Continue',
+    101: 'Switching Protocols',
+
+    200: 'OK',
+    201: 'Created',
+    202: 'Accepted',
+    203: 'Non-Authoritative Information',
+    204: 'No Content',
+    205: 'Reset Content',
+    206: 'Partial Content',
+
+    300: 'Multiple Choices',
+    301: 'Moved Permanently',
+    302: 'Found',
+    303: 'See Other',
+    304: 'Not Modified',
+    305: 'Use Proxy',
+    306: '(Unused)',
+    307: 'Temporary Redirect',
+
+    400: 'Bad Request',
+    401: 'Unauthorized',
+    402: 'Payment Required',
+    403: 'Forbidden',
+    404: 'Not Found',
+    405: 'Method Not Allowed',
+    406: 'Not Acceptable',
+    407: 'Proxy Authentication Required',
+    408: 'Request Timeout',
+    409: 'Conflict',
+    410: 'Gone',
+    411: 'Length Required',
+    412: 'Precondition Failed',
+    413: 'Request Entity Too Large',
+    414: 'Request-URI Too Long',
+    415: 'Unsupported Media Type',
+    416: 'Requested Range Not Satisfiable',
+    417: 'Expectation Failed',
+
+    500: 'Internal Server Error',
+    501: 'Not Implemented',
+    502: 'Bad Gateway',
+    503: 'Service Unavailable',
+    504: 'Gateway Timeout',
+    505: 'HTTP Version Not Supported',
+}
+
+# Mapping request paths to request handlers:
+__request_handlers__ = {
+    'GET:/remotes.tar.gz': send_remotes_archive,
+    'GET:/upload/success': show_upload_success,
+    'GET:/':               show_upload_form,
+
+    'POST:/upload/':       process_upload,
+}
+
+
+# pylint: disable-msg=W0702,W0704
+# vim: ft=python



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]