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
- From: murrayc svn gnome org
- To: svn-commits-list gnome org
- Subject: 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
- Date: Sat, 19 Apr 2008 10:45:09 +0100 (BST)
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"><small></small></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"><b>IR Receiver</b></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"><b>IR Remote Control</b></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"><none></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"><b>Configuration Test</b></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"><big><b>Multiple Receivers Detected.</b></big>
+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"><big><b>Some Action</b></big></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]